diff options
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 | ||
