forked from dodola/scripts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
publish-package.py
executable file
·293 lines (245 loc) · 11.1 KB
/
publish-package.py
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
#!/usr/bin/env python
# Copyright 2017 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import json
import os
import shutil
import struct
import subprocess
import sys
def gen_pkg_key(pm_bin, dest):
"""Create a key to use for signing FARs.
pm_bin: path to the 'pm' binary
dest : path where the key should be written
If successful None is returned, otherwise a string describing the error
encountered is returned.
"""
pm_cmd = [pm_bin, "-k", dest, "genkey"]
try:
subprocess.check_call(pm_cmd)
return None
except subprocess.CalledProcessError as e:
return "Error generating FAR signing key: %s" % e
except OSError as e:
return "Error launching PM binary: %s" % e
def build_package(pm_bin, pkg_key, far_stg_dir, manifest, pkg_name):
"""Build a metadata FAR describing the package.
pm_bin : path to the 'pm' binary
pkg_key : path to the key to use to sign the FAR
far_stg_dir: a working directory to use for staging files before they are
written into a single file
manifest : path to a manifest file describing the contents of the package
pkg_name : the name of the package
Create a signed metadata FAR representing the package. This function
returns a tuple which is the path to the metadata FAR and any error. If
there is an error the first member of the tuple is None and the second
member contains a string describing the error. If there is no error, the
second member of tuple is None.
"""
init_cmd = [pm_bin, "-o", far_stg_dir, "-n", pkg_name, "init"]
try:
subprocess.check_call(init_cmd)
except subprocess.CalledProcessError as e:
return None, None, "Could not initialize package: %s" % e
except OSError as e:
return None, None, "Could not start package initializer: %s" % e
build_cmd = [pm_bin, "-o", far_stg_dir, "-k", pkg_key, "-m", manifest, "build"]
try:
subprocess.check_call(build_cmd)
except subprocess.CalledProcessError as e:
return None, "Could not create package metadata FAR: %s" % e
except OSError as e:
return None, None, "Could not start packging tool %s" % e
far_path = os.path.join(far_stg_dir, "meta.far")
pkg_json = os.path.join(far_stg_dir, "meta", "package")
if os.path.exists(far_path) and os.path.exists(pkg_json):
return far_path, pkg_json, None
else:
return None, None, "Unknown failure, metadata package not produced"
def assemble_manifest(manifests_dir, output_stream):
"""Create a single manifest from the joining of the system and boot
manifests.
manifests_dir: Directory which contains system_manifest and/or
boot_manifest.
output_stream: An output stream where the combined manifest will be
written.
No returns, but may raise an exception if writing to output_stream fails.
"""
inputs = ["final_package_manifest"]
for input in inputs:
manifest = os.path.join(manifests_dir, input)
if os.path.exists(manifest) and os.stat(manifest).st_size > 0:
with open(manifest, "r") as src:
for line in src:
output_stream.write(line)
break
def add_far_to_repo(amber_bin, name, far, key_dir, repo_dir, version=0):
"""Add a FAR to the update repository under the specified name
amber_bin: path to the amber binary
name : name to publish the package as
far : path to the FAR file to publish
key_dir : directory containing set of keys for the update repository
repo_dir : directory to use as the update repository (should exist, but
doesn't need to be initialized as an update repo)
On success returns None, otherwise returns a string describing the error
that occurred.
"""
cmd = [amber_bin, "-r", repo_dir, "-p", "-f", far, "-n", "%s/%d" % (name, version), "-k", key_dir]
try:
subprocess.check_call(cmd)
except subprocess.CalledProcessError as e:
return "Failure running package publisher: %s" % e
except OSError as e:
return "Failure launching package publisher: %s" % e
return None
def add_rsrcs_to_repo(amber_bin, manifest, key_dir, repo_dir):
"""Add package resources, aka. content blobs to the update repository.
amber_bin: path to the amber binary
manifest : a file containing a mapping of file paths on the target to
paths on the host. All paths will be added to the update
respository, named after their content ID.
key_dir : directory containing set of keys for the update repository
repo_dir : directory to use as the update repository (should exist, but
doesn't need to be initialized as an update repo)
On success returns None, otherwise returns a string describing the error
that occurred.
"""
cmd = [amber_bin, "-r", repo_dir, "-m", "-f", manifest, "-k", key_dir]
try:
subprocess.check_call(cmd)
except subprocess.CalledProcessError as e:
return "Failure adding content blob: %s" % e
except OSError as e:
return "Unable to launch blob tool: %s" % e
return None
def publish(pm_bin, amber_bin, pkg_key, repo_key_dir, pkg_stg_dir, update_repo,
manifests_dir, pkgs, verbose):
"""Publish packages as a signed metadata FAR and a collection of content
blobs named after their content IDs.
pm_bin : path to the pm binary
amber_bin : path to the amber binary
pkg_key : path to the key to use to sign the metdata FARs
repo_key_dir : directory containing keys to use for the update respository
pkg_stg_dir : a directory that can be used for staging temporary files
when creating the packages
update_repo : a path that is or can be used as the update repository
manifests_dir: parent directory where manifests can be found for the
packages
pkgs : pkgs to publish
verbose : print out status updates
"""
# as an optimization, publish the content blobs all at once
master_fest = os.path.join(pkg_stg_dir, "masterfest")
master_fd = open(master_fest, "w+")
count = len(pkgs)
for pkg in pkgs:
# this process can be time consuming with a large number of packages
# so printing incremental progress can be reassuring
if verbose:
sys.stdout.write("Packages remaining: %d \r" % count)
sys.stdout.flush()
far_base = os.path.join(pkg_stg_dir, pkg)
far_stg = os.path.join(far_base, "archive")
if os.path.exists(far_base):
shutil.rmtree(far_base)
os.makedirs(far_stg)
manifest = os.path.join(far_base, "manifest")
src_manifest_dir = os.path.join(manifests_dir, pkg)
with open(manifest, "w+") as man_fd:
assemble_manifest(src_manifest_dir, man_fd)
# some packages are apparently devoid of content, skip them
if os.stat(manifest).st_size == 0:
count -= 1
continue
with open(manifest, "r") as man_fd:
for line in man_fd:
master_fd.write(line)
meta_far, pkg_json, err = build_package(pm_bin, pkg_key, far_stg, manifest, pkg)
if err is not None:
print "Building package failed: %s" % err
break
pkg_version = None
with open(pkg_json, 'r') as pkg_meta:
meta = json.load(pkg_meta)
pkg_version = int(meta["version"])
if pkg_version is None:
print "Could not read version from %q" % pkg_json
break
result = add_far_to_repo(amber_bin, pkg, meta_far, repo_key_dir, update_repo,
version=pkg_version)
if result is not None:
print "Package not added to update repo: %s" % result
break
count -= 1
master_fd.close()
result = add_rsrcs_to_repo(amber_bin, master_fest, repo_key_dir, update_repo)
if result is not None:
print "Package contents not added to update repo: %s" % result
return -1
return count
def main():
parser = argparse.ArgumentParser(description=("Publish one or more build "
"packages as package manager "
"packages."))
parser.add_argument('--build-dir', action='store', required=True)
parser.add_argument('--update-repo', action='store', required=False)
parser.add_argument('--update-keys', action='store', required=False)
parser.add_argument('--pkg-key', action='store', required=False)
parser.add_argument('--fars-dir', action='store', required=False,
help="""Directory where intermediate files for the
package(s) will be stored""")
parser.add_argument('--pkgs', action='append', required=False,
help="""Packages to publish. This argument may be
repeated to publish multiple packages.""")
parser.add_argument('--quiet', action='store_true', required=False, default=False)
args = parser.parse_args()
build_dir = args.build_dir
ptr_size = 8 * struct.calcsize("P")
host_tools_dir = os.path.join(build_dir, "host_x%d" % ptr_size)
pm_bin = os.path.join(host_tools_dir, "pm")
if not os.path.exists(pm_bin):
print "Could not find 'pm' tool at %s" % pm_bin
return -1
amber_bin = os.path.join(host_tools_dir, "amber-publish")
if not os.path.exists(amber_bin):
print "Could not find amber-publish tool at %s" % amber_bin
return -1
repo_dir = args.update_repo
if not repo_dir:
repo_dir = os.path.join(build_dir, "amber-files")
if not os.path.exists(repo_dir):
os.makedirs(repo_dir)
if not os.path.exists(repo_dir):
print "Publishing repository directory '%s' could not be found" % repo_dir
return -1
keys_src_dir = args.update_keys
if not keys_src_dir:
keys_src_dir = build_dir
pkg_key = args.pkg_key
if not pkg_key:
pkg_key = os.path.join(build_dir, "pkg_key")
result = gen_pkg_key(pm_bin, pkg_key)
if result is not None:
print result
return -1
pkg_stg_dir = args.fars_dir
if not pkg_stg_dir:
pkg_stg_dir = os.path.join(build_dir, "fars")
if not os.path.exists(pkg_stg_dir):
os.makedirs(pkg_stg_dir)
if not os.path.exists(pkg_stg_dir):
print "Packages staging directory '%s' could not be found" % pkg_stg_dir
return -1
pkg_list = args.pkgs
if not pkg_list:
pkg_list = []
list_path = os.path.join(build_dir, "gen", "build", "gn", "packages")
with open(list_path, "r") as pfile:
for l in pfile:
pkg_list.append(l.strip())
return publish(pm_bin, amber_bin, pkg_key, build_dir, pkg_stg_dir, repo_dir,
os.path.join(build_dir, "package"), pkg_list, not args.quiet)
if __name__ == '__main__':
sys.exit(main())