diff options
author | Mark Hatle <mark.hatle@windriver.com> | 2018-07-23 22:29:11 -0400 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2018-08-02 10:18:27 +0100 |
commit | 1ac19d1bf111a4836625f5cbb28a751d5c427395 (patch) | |
tree | ae9fa1448b425522952cbc8af90cf79912c746b1 /bitbake/lib/layerindexlib/cooker.py | |
parent | 0dea95093115acc08f6ad19dc931d532a601cbec (diff) | |
download | poky-1ac19d1bf111a4836625f5cbb28a751d5c427395.tar.gz |
bitbake: layerindexlib: Initial layer index processing module implementation
The layer index module is expected to be used by various parts of the system
in order to access a layerindex-web (such as layers.openembedded.org) and
perform basic processing on the information, such as dependency scanning.
Along with the layerindex implementation are associated tests. The tests
properly honor BB_SKIP_NETTESTS='yes' to prevent test failures.
Tests Implemented:
- Branch, LayerItem, LayerBranch, LayerDependency, Recipe, Machine and
Distro objects
- LayerIndex setup using the layers.openembedded.org restapi
- LayerIndex storing and retrieving from a file
- LayerIndex verify dependency resolution ordering
- LayerIndex setup using simulated cooker data
(Bitbake rev: fd0ee6c10dbb5592731e56f4c592fe687682a3e6)
Signed-off-by: Mark Hatle <mark.hatle@windriver.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/lib/layerindexlib/cooker.py')
-rw-r--r-- | bitbake/lib/layerindexlib/cooker.py | 341 |
1 files changed, 341 insertions, 0 deletions
diff --git a/bitbake/lib/layerindexlib/cooker.py b/bitbake/lib/layerindexlib/cooker.py new file mode 100644 index 0000000000..248a597754 --- /dev/null +++ b/bitbake/lib/layerindexlib/cooker.py | |||
@@ -0,0 +1,341 @@ | |||
1 | # Copyright (C) 2016-2018 Wind River Systems, Inc. | ||
2 | # | ||
3 | # This program is free software; you can redistribute it and/or modify | ||
4 | # it under the terms of the GNU General Public License version 2 as | ||
5 | # published by the Free Software Foundation. | ||
6 | # | ||
7 | # This program is distributed in the hope that it will be useful, | ||
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | ||
10 | # See the GNU General Public License for more details. | ||
11 | # | ||
12 | # You should have received a copy of the GNU General Public License | ||
13 | # along with this program; if not, write to the Free Software | ||
14 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
15 | |||
16 | import logging | ||
17 | import json | ||
18 | |||
19 | from collections import OrderedDict, defaultdict | ||
20 | |||
21 | from urllib.parse import unquote, urlparse | ||
22 | |||
23 | import layerindexlib | ||
24 | |||
25 | import layerindexlib.plugin | ||
26 | |||
27 | logger = logging.getLogger('BitBake.layerindexlib.cooker') | ||
28 | |||
29 | import bb.utils | ||
30 | |||
31 | def plugin_init(plugins): | ||
32 | return CookerPlugin() | ||
33 | |||
34 | class CookerPlugin(layerindexlib.plugin.IndexPlugin): | ||
35 | def __init__(self): | ||
36 | self.type = "cooker" | ||
37 | |||
38 | self.server_connection = None | ||
39 | self.ui_module = None | ||
40 | self.server = None | ||
41 | |||
42 | def _run_command(self, command, path, default=None): | ||
43 | try: | ||
44 | result, _ = bb.process.run(command, cwd=path) | ||
45 | result = result.strip() | ||
46 | except bb.process.ExecutionError: | ||
47 | result = default | ||
48 | return result | ||
49 | |||
50 | def _handle_git_remote(self, remote): | ||
51 | if "://" not in remote: | ||
52 | if ':' in remote: | ||
53 | # This is assumed to be ssh | ||
54 | remote = "ssh://" + remote | ||
55 | else: | ||
56 | # This is assumed to be a file path | ||
57 | remote = "file://" + remote | ||
58 | return remote | ||
59 | |||
60 | def _get_bitbake_info(self): | ||
61 | """Return a tuple of bitbake information""" | ||
62 | |||
63 | # Our path SHOULD be .../bitbake/lib/layerindex/cooker.py | ||
64 | bb_path = os.path.dirname(__file__) # .../bitbake/lib/layerindex/cooker.py | ||
65 | bb_path = os.path.dirname(bb_path) # .../bitbake/lib/layerindex | ||
66 | bb_path = os.path.dirname(bb_path) # .../bitbake/lib | ||
67 | bb_path = os.path.dirname(bb_path) # .../bitbake | ||
68 | bb_path = self._run_command('git rev-parse --show-toplevel', os.path.dirname(__file__), default=bb_path) | ||
69 | bb_branch = self._run_command('git rev-parse --abbrev-ref HEAD', bb_path, default="<unknown>") | ||
70 | bb_rev = self._run_command('git rev-parse HEAD', bb_path, default="<unknown>") | ||
71 | for remotes in self._run_command('git remote -v', bb_path, default="").split("\n"): | ||
72 | remote = remotes.split("\t")[1].split(" ")[0] | ||
73 | if "(fetch)" == remotes.split("\t")[1].split(" ")[1]: | ||
74 | bb_remote = self._handle_git_remote(remote) | ||
75 | break | ||
76 | else: | ||
77 | bb_remote = self._handle_git_remote(bb_path) | ||
78 | |||
79 | return (bb_remote, bb_branch, bb_rev, bb_path) | ||
80 | |||
81 | def _load_bblayers(self, branches=None): | ||
82 | """Load the BBLAYERS and related collection information""" | ||
83 | |||
84 | d = self.layerindex.data | ||
85 | |||
86 | if not branches: | ||
87 | raise LayerIndexFetchError("No branches specified for _load_bblayers!") | ||
88 | |||
89 | index = layerindexlib.LayerIndexObj() | ||
90 | |||
91 | branchId = 0 | ||
92 | index.branches = {} | ||
93 | |||
94 | layerItemId = 0 | ||
95 | index.layerItems = {} | ||
96 | |||
97 | layerBranchId = 0 | ||
98 | index.layerBranches = {} | ||
99 | |||
100 | bblayers = d.getVar('BBLAYERS').split() | ||
101 | |||
102 | if not bblayers: | ||
103 | # It's blank! Nothing to process... | ||
104 | return index | ||
105 | |||
106 | collections = d.getVar('BBFILE_COLLECTIONS') | ||
107 | layerconfs = d.varhistory.get_variable_items_files('BBFILE_COLLECTIONS', d) | ||
108 | bbfile_collections = {layer: os.path.dirname(os.path.dirname(path)) for layer, path in layerconfs.items()} | ||
109 | |||
110 | (_, bb_branch, _, _) = self._get_bitbake_info() | ||
111 | |||
112 | for branch in branches: | ||
113 | branchId += 1 | ||
114 | index.branches[branchId] = layerindexlib.Branch(index, None) | ||
115 | index.branches[branchId].define_data(branchId, branch, bb_branch) | ||
116 | |||
117 | for entry in collections.split(): | ||
118 | layerpath = entry | ||
119 | if entry in bbfile_collections: | ||
120 | layerpath = bbfile_collections[entry] | ||
121 | |||
122 | layername = d.getVar('BBLAYERS_LAYERINDEX_NAME_%s' % entry) or os.path.basename(layerpath) | ||
123 | layerversion = d.getVar('LAYERVERSION_%s' % entry) or "" | ||
124 | layerurl = self._handle_git_remote(layerpath) | ||
125 | |||
126 | layersubdir = "" | ||
127 | layerrev = "<unknown>" | ||
128 | layerbranch = "<unknown>" | ||
129 | |||
130 | if os.path.isdir(layerpath): | ||
131 | layerbasepath = self._run_command('git rev-parse --show-toplevel', layerpath, default=layerpath) | ||
132 | if os.path.abspath(layerpath) != os.path.abspath(layerbasepath): | ||
133 | layersubdir = os.path.abspath(layerpath)[len(layerbasepath) + 1:] | ||
134 | |||
135 | layerbranch = self._run_command('git rev-parse --abbrev-ref HEAD', layerpath, default="<unknown>") | ||
136 | layerrev = self._run_command('git rev-parse HEAD', layerpath, default="<unknown>") | ||
137 | |||
138 | for remotes in self._run_command('git remote -v', layerpath, default="").split("\n"): | ||
139 | remote = remotes.split("\t")[1].split(" ")[0] | ||
140 | if "(fetch)" == remotes.split("\t")[1].split(" ")[1]: | ||
141 | layerurl = self._handle_git_remote(remote) | ||
142 | break | ||
143 | |||
144 | layerItemId += 1 | ||
145 | index.layerItems[layerItemId] = layerindexlib.LayerItem(index, None) | ||
146 | index.layerItems[layerItemId].define_data(layerItemId, layername, description=layerpath, vcs_url=layerurl) | ||
147 | |||
148 | for branchId in index.branches: | ||
149 | layerBranchId += 1 | ||
150 | index.layerBranches[layerBranchId] = layerindexlib.LayerBranch(index, None) | ||
151 | index.layerBranches[layerBranchId].define_data(layerBranchId, entry, layerversion, layerItemId, branchId, | ||
152 | vcs_subdir=layersubdir, vcs_last_rev=layerrev, actual_branch=layerbranch) | ||
153 | |||
154 | return index | ||
155 | |||
156 | |||
157 | def load_index(self, url, load): | ||
158 | """ | ||
159 | Fetches layer information from a build configuration. | ||
160 | |||
161 | The return value is a dictionary containing API, | ||
162 | layer, branch, dependency, recipe, machine, distro, information. | ||
163 | |||
164 | url type should be 'cooker'. | ||
165 | url path is ignored | ||
166 | """ | ||
167 | |||
168 | up = urlparse(url) | ||
169 | |||
170 | if up.scheme != 'cooker': | ||
171 | raise layerindexlib.plugin.LayerIndexPluginUrlError(self.type, url) | ||
172 | |||
173 | d = self.layerindex.data | ||
174 | |||
175 | params = self.layerindex._parse_params(up.params) | ||
176 | |||
177 | # Only reason to pass a branch is to emulate them... | ||
178 | if 'branch' in params: | ||
179 | branches = params['branch'].split(',') | ||
180 | else: | ||
181 | branches = ['HEAD'] | ||
182 | |||
183 | logger.debug(1, "Loading cooker data branches %s" % branches) | ||
184 | |||
185 | index = self._load_bblayers(branches=branches) | ||
186 | |||
187 | index.config = {} | ||
188 | index.config['TYPE'] = self.type | ||
189 | index.config['URL'] = url | ||
190 | |||
191 | if 'desc' in params: | ||
192 | index.config['DESCRIPTION'] = unquote(params['desc']) | ||
193 | else: | ||
194 | index.config['DESCRIPTION'] = 'local' | ||
195 | |||
196 | if 'cache' in params: | ||
197 | index.config['CACHE'] = params['cache'] | ||
198 | |||
199 | index.config['BRANCH'] = branches | ||
200 | |||
201 | # ("layerDependencies", layerindexlib.LayerDependency) | ||
202 | layerDependencyId = 0 | ||
203 | if "layerDependencies" in load: | ||
204 | index.layerDependencies = {} | ||
205 | for layerBranchId in index.layerBranches: | ||
206 | branchName = index.layerBranches[layerBranchId].branch.name | ||
207 | collection = index.layerBranches[layerBranchId].collection | ||
208 | |||
209 | def add_dependency(layerDependencyId, index, deps, required): | ||
210 | try: | ||
211 | depDict = bb.utils.explode_dep_versions2(deps) | ||
212 | except bb.utils.VersionStringException as vse: | ||
213 | bb.fatal('Error parsing LAYERDEPENDS_%s: %s' % (c, str(vse))) | ||
214 | |||
215 | for dep, oplist in list(depDict.items()): | ||
216 | # We need to search ourselves, so use the _ version... | ||
217 | depLayerBranch = index.find_collection(dep, branches=[branchName]) | ||
218 | if not depLayerBranch: | ||
219 | # Missing dependency?! | ||
220 | logger.error('Missing dependency %s (%s)' % (dep, branchName)) | ||
221 | continue | ||
222 | |||
223 | # We assume that the oplist matches... | ||
224 | layerDependencyId += 1 | ||
225 | layerDependency = layerindexlib.LayerDependency(index, None) | ||
226 | layerDependency.define_data(id=layerDependencyId, | ||
227 | required=required, layerbranch=layerBranchId, | ||
228 | dependency=depLayerBranch.layer_id) | ||
229 | |||
230 | logger.debug(1, '%s requires %s' % (layerDependency.layer.name, layerDependency.dependency.name)) | ||
231 | index.add_element("layerDependencies", [layerDependency]) | ||
232 | |||
233 | return layerDependencyId | ||
234 | |||
235 | deps = d.getVar("LAYERDEPENDS_%s" % collection) | ||
236 | if deps: | ||
237 | layerDependencyId = add_dependency(layerDependencyId, index, deps, True) | ||
238 | |||
239 | deps = d.getVar("LAYERRECOMMENDS_%s" % collection) | ||
240 | if deps: | ||
241 | layerDependencyId = add_dependency(layerDependencyId, index, deps, False) | ||
242 | |||
243 | # Need to load recipes here (requires cooker access) | ||
244 | recipeId = 0 | ||
245 | ## TODO: NOT IMPLEMENTED | ||
246 | # The code following this is an example of what needs to be | ||
247 | # implemented. However, it does not work as-is. | ||
248 | if False and 'recipes' in load: | ||
249 | index.recipes = {} | ||
250 | |||
251 | ret = self.ui_module.main(self.server_connection.connection, self.server_connection.events, config_params) | ||
252 | |||
253 | all_versions = self._run_command('allProviders') | ||
254 | |||
255 | all_versions_list = defaultdict(list, all_versions) | ||
256 | for pn in all_versions_list: | ||
257 | for ((pe, pv, pr), fpath) in all_versions_list[pn]: | ||
258 | realfn = bb.cache.virtualfn2realfn(fpath) | ||
259 | |||
260 | filepath = os.path.dirname(realfn[0]) | ||
261 | filename = os.path.basename(realfn[0]) | ||
262 | |||
263 | # This is all HORRIBLY slow, and likely unnecessary | ||
264 | #dscon = self._run_command('parseRecipeFile', fpath, False, []) | ||
265 | #connector = myDataStoreConnector(self, dscon.dsindex) | ||
266 | #recipe_data = bb.data.init() | ||
267 | #recipe_data.setVar('_remote_data', connector) | ||
268 | |||
269 | #summary = recipe_data.getVar('SUMMARY') | ||
270 | #description = recipe_data.getVar('DESCRIPTION') | ||
271 | #section = recipe_data.getVar('SECTION') | ||
272 | #license = recipe_data.getVar('LICENSE') | ||
273 | #homepage = recipe_data.getVar('HOMEPAGE') | ||
274 | #bugtracker = recipe_data.getVar('BUGTRACKER') | ||
275 | #provides = recipe_data.getVar('PROVIDES') | ||
276 | |||
277 | layer = bb.utils.get_file_layer(realfn[0], self.config_data) | ||
278 | |||
279 | depBranchId = collection_layerbranch[layer] | ||
280 | |||
281 | recipeId += 1 | ||
282 | recipe = layerindexlib.Recipe(index, None) | ||
283 | recipe.define_data(id=recipeId, | ||
284 | filename=filename, filepath=filepath, | ||
285 | pn=pn, pv=pv, | ||
286 | summary=pn, description=pn, section='?', | ||
287 | license='?', homepage='?', bugtracker='?', | ||
288 | provides='?', bbclassextend='?', inherits='?', | ||
289 | blacklisted='?', layerbranch=depBranchId) | ||
290 | |||
291 | index = addElement("recipes", [recipe], index) | ||
292 | |||
293 | # ("machines", layerindexlib.Machine) | ||
294 | machineId = 0 | ||
295 | if 'machines' in load: | ||
296 | index.machines = {} | ||
297 | |||
298 | for layerBranchId in index.layerBranches: | ||
299 | # load_bblayers uses the description to cache the actual path... | ||
300 | machine_path = index.layerBranches[layerBranchId].getDescription() | ||
301 | machine_path = os.path.join(machine_path, 'conf/machine') | ||
302 | if os.path.isdir(machine_path): | ||
303 | for (dirpath, _, filenames) in os.walk(machine_path): | ||
304 | # Ignore subdirs... | ||
305 | if not dirpath.endswith('conf/machine'): | ||
306 | continue | ||
307 | for fname in filenames: | ||
308 | if fname.endswith('.conf'): | ||
309 | machineId += 1 | ||
310 | machine = layerindexlib.Machine(index, None) | ||
311 | machine.define_data(id=machineId, name=fname[:-5], | ||
312 | description=fname[:-5], | ||
313 | layerbranch=collection_layerbranch[entry]) | ||
314 | |||
315 | index.add_element("machines", [machine]) | ||
316 | |||
317 | # ("distros", layerindexlib.Distro) | ||
318 | distroId = 0 | ||
319 | if 'distros' in load: | ||
320 | index.distros = {} | ||
321 | |||
322 | for layerBranchId in index.layerBranches: | ||
323 | # load_bblayers uses the description to cache the actual path... | ||
324 | distro_path = index.layerBranches[layerBranchId].getDescription() | ||
325 | distro_path = os.path.join(distro_path, 'conf/distro') | ||
326 | if os.path.isdir(distro_path): | ||
327 | for (dirpath, _, filenames) in os.walk(distro_path): | ||
328 | # Ignore subdirs... | ||
329 | if not dirpath.endswith('conf/distro'): | ||
330 | continue | ||
331 | for fname in filenames: | ||
332 | if fname.endswith('.conf'): | ||
333 | distroId += 1 | ||
334 | distro = layerindexlib.Distro(index, None) | ||
335 | distro.define_data(id=distroId, name=fname[:-5], | ||
336 | description=fname[:-5], | ||
337 | layerbranch=collection_layerbranch[entry]) | ||
338 | |||
339 | index.add_element("distros", [distro]) | ||
340 | |||
341 | return index | ||