Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 1 | # Copyright (C) 2016-2018 Wind River Systems, Inc. |
| 2 | # |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 3 | # SPDX-License-Identifier: GPL-2.0-only |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 4 | # |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 5 | |
| 6 | import logging |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 7 | |
Andrew Geissler | 82c905d | 2020-04-13 13:39:40 -0500 | [diff] [blame] | 8 | from collections import defaultdict |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 9 | |
| 10 | from urllib.parse import unquote, urlparse |
| 11 | |
| 12 | import layerindexlib |
| 13 | |
| 14 | import layerindexlib.plugin |
| 15 | |
| 16 | logger = logging.getLogger('BitBake.layerindexlib.cooker') |
| 17 | |
| 18 | import bb.utils |
| 19 | |
| 20 | def plugin_init(plugins): |
| 21 | return CookerPlugin() |
| 22 | |
| 23 | class CookerPlugin(layerindexlib.plugin.IndexPlugin): |
| 24 | def __init__(self): |
| 25 | self.type = "cooker" |
| 26 | |
| 27 | self.server_connection = None |
| 28 | self.ui_module = None |
| 29 | self.server = None |
| 30 | |
| 31 | def _run_command(self, command, path, default=None): |
| 32 | try: |
| 33 | result, _ = bb.process.run(command, cwd=path) |
| 34 | result = result.strip() |
| 35 | except bb.process.ExecutionError: |
| 36 | result = default |
| 37 | return result |
| 38 | |
| 39 | def _handle_git_remote(self, remote): |
| 40 | if "://" not in remote: |
| 41 | if ':' in remote: |
| 42 | # This is assumed to be ssh |
| 43 | remote = "ssh://" + remote |
| 44 | else: |
| 45 | # This is assumed to be a file path |
| 46 | remote = "file://" + remote |
| 47 | return remote |
| 48 | |
| 49 | def _get_bitbake_info(self): |
| 50 | """Return a tuple of bitbake information""" |
| 51 | |
| 52 | # Our path SHOULD be .../bitbake/lib/layerindex/cooker.py |
| 53 | bb_path = os.path.dirname(__file__) # .../bitbake/lib/layerindex/cooker.py |
| 54 | bb_path = os.path.dirname(bb_path) # .../bitbake/lib/layerindex |
| 55 | bb_path = os.path.dirname(bb_path) # .../bitbake/lib |
| 56 | bb_path = os.path.dirname(bb_path) # .../bitbake |
| 57 | bb_path = self._run_command('git rev-parse --show-toplevel', os.path.dirname(__file__), default=bb_path) |
| 58 | bb_branch = self._run_command('git rev-parse --abbrev-ref HEAD', bb_path, default="<unknown>") |
| 59 | bb_rev = self._run_command('git rev-parse HEAD', bb_path, default="<unknown>") |
| 60 | for remotes in self._run_command('git remote -v', bb_path, default="").split("\n"): |
| 61 | remote = remotes.split("\t")[1].split(" ")[0] |
| 62 | if "(fetch)" == remotes.split("\t")[1].split(" ")[1]: |
| 63 | bb_remote = self._handle_git_remote(remote) |
| 64 | break |
| 65 | else: |
| 66 | bb_remote = self._handle_git_remote(bb_path) |
| 67 | |
| 68 | return (bb_remote, bb_branch, bb_rev, bb_path) |
| 69 | |
| 70 | def _load_bblayers(self, branches=None): |
| 71 | """Load the BBLAYERS and related collection information""" |
| 72 | |
| 73 | d = self.layerindex.data |
| 74 | |
| 75 | if not branches: |
| 76 | raise LayerIndexFetchError("No branches specified for _load_bblayers!") |
| 77 | |
| 78 | index = layerindexlib.LayerIndexObj() |
| 79 | |
| 80 | branchId = 0 |
| 81 | index.branches = {} |
| 82 | |
| 83 | layerItemId = 0 |
| 84 | index.layerItems = {} |
| 85 | |
| 86 | layerBranchId = 0 |
| 87 | index.layerBranches = {} |
| 88 | |
| 89 | bblayers = d.getVar('BBLAYERS').split() |
| 90 | |
| 91 | if not bblayers: |
| 92 | # It's blank! Nothing to process... |
| 93 | return index |
| 94 | |
| 95 | collections = d.getVar('BBFILE_COLLECTIONS') |
Andrew Geissler | 82c905d | 2020-04-13 13:39:40 -0500 | [diff] [blame] | 96 | layerconfs = d.varhistory.get_variable_items_files('BBFILE_COLLECTIONS') |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 97 | bbfile_collections = {layer: os.path.dirname(os.path.dirname(path)) for layer, path in layerconfs.items()} |
| 98 | |
| 99 | (_, bb_branch, _, _) = self._get_bitbake_info() |
| 100 | |
| 101 | for branch in branches: |
| 102 | branchId += 1 |
| 103 | index.branches[branchId] = layerindexlib.Branch(index, None) |
| 104 | index.branches[branchId].define_data(branchId, branch, bb_branch) |
| 105 | |
| 106 | for entry in collections.split(): |
| 107 | layerpath = entry |
| 108 | if entry in bbfile_collections: |
| 109 | layerpath = bbfile_collections[entry] |
| 110 | |
| 111 | layername = d.getVar('BBLAYERS_LAYERINDEX_NAME_%s' % entry) or os.path.basename(layerpath) |
| 112 | layerversion = d.getVar('LAYERVERSION_%s' % entry) or "" |
| 113 | layerurl = self._handle_git_remote(layerpath) |
| 114 | |
| 115 | layersubdir = "" |
| 116 | layerrev = "<unknown>" |
| 117 | layerbranch = "<unknown>" |
| 118 | |
| 119 | if os.path.isdir(layerpath): |
| 120 | layerbasepath = self._run_command('git rev-parse --show-toplevel', layerpath, default=layerpath) |
| 121 | if os.path.abspath(layerpath) != os.path.abspath(layerbasepath): |
| 122 | layersubdir = os.path.abspath(layerpath)[len(layerbasepath) + 1:] |
| 123 | |
| 124 | layerbranch = self._run_command('git rev-parse --abbrev-ref HEAD', layerpath, default="<unknown>") |
| 125 | layerrev = self._run_command('git rev-parse HEAD', layerpath, default="<unknown>") |
| 126 | |
| 127 | for remotes in self._run_command('git remote -v', layerpath, default="").split("\n"): |
| 128 | if not remotes: |
| 129 | layerurl = self._handle_git_remote(layerpath) |
| 130 | else: |
| 131 | remote = remotes.split("\t")[1].split(" ")[0] |
| 132 | if "(fetch)" == remotes.split("\t")[1].split(" ")[1]: |
| 133 | layerurl = self._handle_git_remote(remote) |
| 134 | break |
| 135 | |
| 136 | layerItemId += 1 |
| 137 | index.layerItems[layerItemId] = layerindexlib.LayerItem(index, None) |
| 138 | index.layerItems[layerItemId].define_data(layerItemId, layername, description=layerpath, vcs_url=layerurl) |
| 139 | |
| 140 | for branchId in index.branches: |
| 141 | layerBranchId += 1 |
| 142 | index.layerBranches[layerBranchId] = layerindexlib.LayerBranch(index, None) |
| 143 | index.layerBranches[layerBranchId].define_data(layerBranchId, entry, layerversion, layerItemId, branchId, |
| 144 | vcs_subdir=layersubdir, vcs_last_rev=layerrev, actual_branch=layerbranch) |
| 145 | |
| 146 | return index |
| 147 | |
| 148 | |
| 149 | def load_index(self, url, load): |
| 150 | """ |
| 151 | Fetches layer information from a build configuration. |
| 152 | |
| 153 | The return value is a dictionary containing API, |
| 154 | layer, branch, dependency, recipe, machine, distro, information. |
| 155 | |
| 156 | url type should be 'cooker'. |
| 157 | url path is ignored |
| 158 | """ |
| 159 | |
| 160 | up = urlparse(url) |
| 161 | |
| 162 | if up.scheme != 'cooker': |
| 163 | raise layerindexlib.plugin.LayerIndexPluginUrlError(self.type, url) |
| 164 | |
| 165 | d = self.layerindex.data |
| 166 | |
| 167 | params = self.layerindex._parse_params(up.params) |
| 168 | |
| 169 | # Only reason to pass a branch is to emulate them... |
| 170 | if 'branch' in params: |
| 171 | branches = params['branch'].split(',') |
| 172 | else: |
| 173 | branches = ['HEAD'] |
| 174 | |
| 175 | logger.debug(1, "Loading cooker data branches %s" % branches) |
| 176 | |
| 177 | index = self._load_bblayers(branches=branches) |
| 178 | |
| 179 | index.config = {} |
| 180 | index.config['TYPE'] = self.type |
| 181 | index.config['URL'] = url |
| 182 | |
| 183 | if 'desc' in params: |
| 184 | index.config['DESCRIPTION'] = unquote(params['desc']) |
| 185 | else: |
| 186 | index.config['DESCRIPTION'] = 'local' |
| 187 | |
| 188 | if 'cache' in params: |
| 189 | index.config['CACHE'] = params['cache'] |
| 190 | |
| 191 | index.config['BRANCH'] = branches |
| 192 | |
| 193 | # ("layerDependencies", layerindexlib.LayerDependency) |
| 194 | layerDependencyId = 0 |
| 195 | if "layerDependencies" in load: |
| 196 | index.layerDependencies = {} |
| 197 | for layerBranchId in index.layerBranches: |
| 198 | branchName = index.layerBranches[layerBranchId].branch.name |
| 199 | collection = index.layerBranches[layerBranchId].collection |
| 200 | |
| 201 | def add_dependency(layerDependencyId, index, deps, required): |
| 202 | try: |
| 203 | depDict = bb.utils.explode_dep_versions2(deps) |
| 204 | except bb.utils.VersionStringException as vse: |
| 205 | bb.fatal('Error parsing LAYERDEPENDS_%s: %s' % (c, str(vse))) |
| 206 | |
| 207 | for dep, oplist in list(depDict.items()): |
| 208 | # We need to search ourselves, so use the _ version... |
| 209 | depLayerBranch = index.find_collection(dep, branches=[branchName]) |
| 210 | if not depLayerBranch: |
| 211 | # Missing dependency?! |
| 212 | logger.error('Missing dependency %s (%s)' % (dep, branchName)) |
| 213 | continue |
| 214 | |
| 215 | # We assume that the oplist matches... |
| 216 | layerDependencyId += 1 |
| 217 | layerDependency = layerindexlib.LayerDependency(index, None) |
| 218 | layerDependency.define_data(id=layerDependencyId, |
| 219 | required=required, layerbranch=layerBranchId, |
| 220 | dependency=depLayerBranch.layer_id) |
| 221 | |
| 222 | logger.debug(1, '%s requires %s' % (layerDependency.layer.name, layerDependency.dependency.name)) |
| 223 | index.add_element("layerDependencies", [layerDependency]) |
| 224 | |
| 225 | return layerDependencyId |
| 226 | |
| 227 | deps = d.getVar("LAYERDEPENDS_%s" % collection) |
| 228 | if deps: |
| 229 | layerDependencyId = add_dependency(layerDependencyId, index, deps, True) |
| 230 | |
| 231 | deps = d.getVar("LAYERRECOMMENDS_%s" % collection) |
| 232 | if deps: |
| 233 | layerDependencyId = add_dependency(layerDependencyId, index, deps, False) |
| 234 | |
| 235 | # Need to load recipes here (requires cooker access) |
| 236 | recipeId = 0 |
| 237 | ## TODO: NOT IMPLEMENTED |
| 238 | # The code following this is an example of what needs to be |
| 239 | # implemented. However, it does not work as-is. |
| 240 | if False and 'recipes' in load: |
| 241 | index.recipes = {} |
| 242 | |
| 243 | ret = self.ui_module.main(self.server_connection.connection, self.server_connection.events, config_params) |
| 244 | |
| 245 | all_versions = self._run_command('allProviders') |
| 246 | |
| 247 | all_versions_list = defaultdict(list, all_versions) |
| 248 | for pn in all_versions_list: |
| 249 | for ((pe, pv, pr), fpath) in all_versions_list[pn]: |
| 250 | realfn = bb.cache.virtualfn2realfn(fpath) |
| 251 | |
| 252 | filepath = os.path.dirname(realfn[0]) |
| 253 | filename = os.path.basename(realfn[0]) |
| 254 | |
| 255 | # This is all HORRIBLY slow, and likely unnecessary |
| 256 | #dscon = self._run_command('parseRecipeFile', fpath, False, []) |
| 257 | #connector = myDataStoreConnector(self, dscon.dsindex) |
| 258 | #recipe_data = bb.data.init() |
| 259 | #recipe_data.setVar('_remote_data', connector) |
| 260 | |
| 261 | #summary = recipe_data.getVar('SUMMARY') |
| 262 | #description = recipe_data.getVar('DESCRIPTION') |
| 263 | #section = recipe_data.getVar('SECTION') |
| 264 | #license = recipe_data.getVar('LICENSE') |
| 265 | #homepage = recipe_data.getVar('HOMEPAGE') |
| 266 | #bugtracker = recipe_data.getVar('BUGTRACKER') |
| 267 | #provides = recipe_data.getVar('PROVIDES') |
| 268 | |
| 269 | layer = bb.utils.get_file_layer(realfn[0], self.config_data) |
| 270 | |
| 271 | depBranchId = collection_layerbranch[layer] |
| 272 | |
| 273 | recipeId += 1 |
| 274 | recipe = layerindexlib.Recipe(index, None) |
| 275 | recipe.define_data(id=recipeId, |
| 276 | filename=filename, filepath=filepath, |
| 277 | pn=pn, pv=pv, |
| 278 | summary=pn, description=pn, section='?', |
| 279 | license='?', homepage='?', bugtracker='?', |
| 280 | provides='?', bbclassextend='?', inherits='?', |
| 281 | blacklisted='?', layerbranch=depBranchId) |
| 282 | |
| 283 | index = addElement("recipes", [recipe], index) |
| 284 | |
| 285 | # ("machines", layerindexlib.Machine) |
| 286 | machineId = 0 |
| 287 | if 'machines' in load: |
| 288 | index.machines = {} |
| 289 | |
| 290 | for layerBranchId in index.layerBranches: |
| 291 | # load_bblayers uses the description to cache the actual path... |
| 292 | machine_path = index.layerBranches[layerBranchId].layer.description |
| 293 | machine_path = os.path.join(machine_path, 'conf/machine') |
| 294 | if os.path.isdir(machine_path): |
| 295 | for (dirpath, _, filenames) in os.walk(machine_path): |
| 296 | # Ignore subdirs... |
| 297 | if not dirpath.endswith('conf/machine'): |
| 298 | continue |
| 299 | for fname in filenames: |
| 300 | if fname.endswith('.conf'): |
| 301 | machineId += 1 |
| 302 | machine = layerindexlib.Machine(index, None) |
| 303 | machine.define_data(id=machineId, name=fname[:-5], |
| 304 | description=fname[:-5], |
| 305 | layerbranch=index.layerBranches[layerBranchId]) |
| 306 | |
| 307 | index.add_element("machines", [machine]) |
| 308 | |
| 309 | # ("distros", layerindexlib.Distro) |
| 310 | distroId = 0 |
| 311 | if 'distros' in load: |
| 312 | index.distros = {} |
| 313 | |
| 314 | for layerBranchId in index.layerBranches: |
| 315 | # load_bblayers uses the description to cache the actual path... |
| 316 | distro_path = index.layerBranches[layerBranchId].layer.description |
| 317 | distro_path = os.path.join(distro_path, 'conf/distro') |
| 318 | if os.path.isdir(distro_path): |
| 319 | for (dirpath, _, filenames) in os.walk(distro_path): |
| 320 | # Ignore subdirs... |
| 321 | if not dirpath.endswith('conf/distro'): |
| 322 | continue |
| 323 | for fname in filenames: |
| 324 | if fname.endswith('.conf'): |
| 325 | distroId += 1 |
| 326 | distro = layerindexlib.Distro(index, None) |
| 327 | distro.define_data(id=distroId, name=fname[:-5], |
| 328 | description=fname[:-5], |
| 329 | layerbranch=index.layerBranches[layerBranchId]) |
| 330 | |
| 331 | index.add_element("distros", [distro]) |
| 332 | |
| 333 | return index |