-
Notifications
You must be signed in to change notification settings - Fork 0
/
comps-sync.py
executable file
·217 lines (185 loc) · 8.7 KB
/
comps-sync.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
#!/usr/bin/python3
'''
Usage: ./comps-sync.py [--save] /path/to/comps-f41.xml.in
Filter and sync packages from comps groups into rpm-ostree manifests. The sync
will remove packages from the manifests which are not mentioned in comps and
add missing packages from comps to the manifests.
Use --save to write the changes and always exit with a 0 return code.
Otherwise, exit with a non zero return code if any changes are needed.
'''
import argparse
import re
import sys
import yaml
import libcomps
ARCHES = ("x86_64", "aarch64", "ppc64le")
def fatal(msg):
'''Print the error message and exit.'''
print(msg, file = sys.stderr)
sys.exit(1)
def format_pkgtype(pkgtype):
'''Return a printable string from a libcomps package type.'''
if pkgtype == libcomps.PACKAGE_TYPE_DEFAULT:
return 'default'
if pkgtype == libcomps.PACKAGE_TYPE_MANDATORY:
return 'mandatory'
assert False
def write_manifest(fpath, pkgs):
'''Write the package list in a manifest.'''
with open(fpath, 'w', encoding='UTF-8') as f:
f.write("# DO NOT EDIT! This content is generated from comps-sync.py\n")
f.write("packages:\n")
for pkg in sorted(pkgs['all']):
f.write(f' - {pkg}\n')
for arch in ARCHES:
if pkgs[arch]:
f.write(f"packages-{arch}:\n")
for pkg in sorted(pkgs[arch]):
f.write(f' - {pkg}\n')
print(f'Wrote {fpath}')
def is_exclude_listed(pkgname, exclude_list_regexp):
'''Check if pkgname is in the exclude list.'''
for br in exclude_list_regexp:
if br.match(pkgname):
return True
return False
def load_packages_from_manifest(manifest_path):
'''Load the list of packages from an rpm-ostree manifest file.'''
with open(manifest_path, encoding='UTF-8') as f:
manifest = yaml.safe_load(f)
manifest_packages = {}
manifest_packages['all'] = set(manifest['packages'])
for arch in ARCHES:
if f'packages-{arch}' in manifest:
manifest_packages[arch] = set(manifest[f'packages-{arch}'])
else:
manifest_packages[arch] = set()
return manifest_packages
def load_packages_from_comps_group(comps_group_packages, comps, groupname, exclude_list, exclude_list_regexp):
'''Load packages from a comps group, storing the group, type and arches.'''
for arch in ARCHES:
filtered = comps.arch_filter([arch])
group = filtered.groups_match(id=groupname)[0]
for pkg in group.packages:
pkgname = pkg.name
if pkg.type not in (libcomps.PACKAGE_TYPE_DEFAULT,
libcomps.PACKAGE_TYPE_MANDATORY):
continue
if pkgname in exclude_list or is_exclude_listed(pkgname, exclude_list_regexp):
continue
pkgdata = comps_group_packages.get(pkgname)
if pkgdata is None:
comps_group_packages[pkgname] = pkgdata = (pkg.type, set([groupname]), set([arch]))
if (pkgdata[0] == libcomps.PACKAGE_TYPE_DEFAULT and
pkg.type == libcomps.PACKAGE_TYPE_MANDATORY):
comps_group_packages[pkgname] = pkgdata = (pkg.type, pkgdata[1], pkgdata[2])
pkgdata[1].add(groupname)
pkgdata[2].add(arch)
return comps_group_packages
def compare_comps_manifest_package_lists(comps_group_pkgs, manifest_packages):
'''Compare the list of packages in the comps and the manifests and return the difference.'''
# Look for packages in the manifest but not in the comps
comps_unknown = set()
for arch in manifest_packages:
for pkg in manifest_packages[arch]:
if arch == "all":
if pkg in comps_group_pkgs and set(comps_group_pkgs[pkg][2]) == set(ARCHES):
continue
else:
if pkg in comps_group_pkgs and arch in comps_group_pkgs[pkg][2]:
continue
comps_unknown.add((pkg, arch))
# Look for packages in comps but not in the manifest
pkgs_added = {}
for (pkg, pkgdata) in comps_group_pkgs.items():
if set(ARCHES) == set(pkgdata[2]):
if pkg not in manifest_packages['all']:
pkgs_added[pkg] = pkgdata
else:
for arch in pkgdata[2]:
if pkg not in manifest_packages[arch]:
if pkg not in pkgs_added:
pkgs_added[pkg] = (pkgdata[0], pkgdata[1], set([arch]))
else:
pkgs_added[pkg][2].add(arch)
return comps_unknown, pkgs_added
def update_manifests_from_groups(comps, groups, path, variant, save, comps_exclude_list, comps_exclude_list_all):
manifest_packages = load_packages_from_manifest(path)
comps_group_pkgs = {}
for group in groups:
exclude_list = comps_exclude_list.get(group, set())
comps_group_pkgs = load_packages_from_comps_group(comps_group_pkgs, comps, group, exclude_list, comps_exclude_list_all)
(comps_unknown, pkgs_added) = compare_comps_manifest_package_lists(comps_group_pkgs, manifest_packages)
n_manifest_new = len(comps_unknown)
n_comps_new = len(pkgs_added)
if variant == "common":
print(f'Syncing common packages:\t+{n_comps_new}, -{n_manifest_new}')
else:
print(f'Syncing packages for {variant}:\t+{n_comps_new}, -{n_manifest_new}')
if n_manifest_new != 0:
for (pkg, arch) in sorted(comps_unknown, key = lambda x: x[0]):
manifest_packages[arch].remove(pkg)
print(f' - {pkg} (arches: {arch})')
if n_comps_new != 0:
for pkg in sorted(pkgs_added):
(req, groups, arches) = pkgs_added[pkg]
if set(ARCHES) == arches:
manifest_packages['all'].add(pkg)
print(' + {} ({}, groups: {}, arches: all)'.format(pkg, format_pkgtype(req), ', '.join(groups)))
else:
for arch in arches:
manifest_packages[arch].add(pkg)
print(' + {} ({}, groups: {}, arches: {})'.format(pkg, format_pkgtype(req), ', '.join(groups), ', '.join(arches)))
if (n_manifest_new > 0 or n_comps_new > 0):
if save:
write_manifest(path, manifest_packages)
return 1
return 0
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--save", help="Write changes to manifests", action='store_true')
parser.add_argument("src", help="Source path")
args = parser.parse_args()
with open('comps-sync-exclude-list.yml', encoding='UTF-8') as f:
doc = yaml.safe_load(f)
comps_exclude_list = doc['exclude_list']
comps_exclude_list_groups = doc['exclude_list_groups']
comps_desktop_exclude_list = doc['desktop_exclude_list']
comps_exclude_list_all = [re.compile(x) for x in doc['exclude_list_all_regexp']]
# Parse comps, and build up a set of all packages so we can find packages not
# listed in comps *at all*, beyond just the workstation environment.
comps = libcomps.Comps()
comps.fromxml_f(args.src)
# Parse the workstation-product environment to get the list of comps groups to
# get packages from.
groups = []
for gid in comps.environments['workstation-product-environment'].group_ids:
if gid.name in comps_exclude_list_groups:
continue
groups.append(gid.name)
# Always include the packages from the workstation-ostree-support group
groups.append('workstation-ostree-support')
# Return code indicates if changes have or would have been done
ret = 0
ret += update_manifests_from_groups(comps, groups, 'common-packages.yaml', "common", args.save, comps_exclude_list, comps_exclude_list_all)
# List of comps groups used for each variant
variant_comps_groups = {
"budgie-atomic": ["budgie-desktop", "budgie-desktop-apps", "base-x"],
"cinnamon-atomic": ["cinnamon-desktop", "base-x"],
"deepin-atomic": ["deepin-desktop", "base-x"],
"kinoite": ["kde-desktop", "base-graphical"],
"kinoite-mobile": ["kde-mobile", "kde-mobile-apps", "base-graphical"],
"lxqt-atomic": ["lxqt-desktop", "base-graphical"],
"mate-atomic": ["mate-desktop", "base-x"],
"silverblue": ["gnome-desktop", "base-graphical"],
"sway-atomic": ["swaywm", "swaywm-extended", "base-graphical"],
"xfce-atomic": ["xfce-desktop", "xfce-apps", "xfce-extra-plugins", "base-x"],
}
# Generate treefiles for all variants
for variant, groups in variant_comps_groups.items():
print()
ret += update_manifests_from_groups(comps, groups, f'{variant}-packages.yaml', variant, args.save, comps_desktop_exclude_list, comps_exclude_list_all)
if not args.save and ret != 0:
sys.exit(1)
if __name__ == "__main__":
main()