Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 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 | if not remotes: |
| 140 | layerurl = self._handle_git_remote(layerpath) |
| 141 | else: |
| 142 | remote = remotes.split("\t")[1].split(" ")[0] |
| 143 | if "(fetch)" == remotes.split("\t")[1].split(" ")[1]: |
| 144 | layerurl = self._handle_git_remote(remote) |
| 145 | break |
| 146 | |
| 147 | layerItemId += 1 |
| 148 | index.layerItems[layerItemId] = layerindexlib.LayerItem(index, None) |
| 149 | index.layerItems[layerItemId].define_data(layerItemId, layername, description=layerpath, vcs_url=layerurl) |
| 150 | |
| 151 | for branchId in index.branches: |
| 152 | layerBranchId += 1 |
| 153 | index.layerBranches[layerBranchId] = layerindexlib.LayerBranch(index, None) |
| 154 | index.layerBranches[layerBranchId].define_data(layerBranchId, entry, layerversion, layerItemId, branchId, |
| 155 | vcs_subdir=layersubdir, vcs_last_rev=layerrev, actual_branch=layerbranch) |
| 156 | |
| 157 | return index |
| 158 | |
| 159 | |
| 160 | def load_index(self, url, load): |
| 161 | """ |
| 162 | Fetches layer information from a build configuration. |
| 163 | |
| 164 | The return value is a dictionary containing API, |
| 165 | layer, branch, dependency, recipe, machine, distro, information. |
| 166 | |
| 167 | url type should be 'cooker'. |
| 168 | url path is ignored |
| 169 | """ |
| 170 | |
| 171 | up = urlparse(url) |
| 172 | |
| 173 | if up.scheme != 'cooker': |
| 174 | raise layerindexlib.plugin.LayerIndexPluginUrlError(self.type, url) |
| 175 | |
| 176 | d = self.layerindex.data |
| 177 | |
| 178 | params = self.layerindex._parse_params(up.params) |
| 179 | |
| 180 | # Only reason to pass a branch is to emulate them... |
| 181 | if 'branch' in params: |
| 182 | branches = params['branch'].split(',') |
| 183 | else: |
| 184 | branches = ['HEAD'] |
| 185 | |
| 186 | logger.debug(1, "Loading cooker data branches %s" % branches) |
| 187 | |
| 188 | index = self._load_bblayers(branches=branches) |
| 189 | |
| 190 | index.config = {} |
| 191 | index.config['TYPE'] = self.type |
| 192 | index.config['URL'] = url |
| 193 | |
| 194 | if 'desc' in params: |
| 195 | index.config['DESCRIPTION'] = unquote(params['desc']) |
| 196 | else: |
| 197 | index.config['DESCRIPTION'] = 'local' |
| 198 | |
| 199 | if 'cache' in params: |
| 200 | index.config['CACHE'] = params['cache'] |
| 201 | |
| 202 | index.config['BRANCH'] = branches |
| 203 | |
| 204 | # ("layerDependencies", layerindexlib.LayerDependency) |
| 205 | layerDependencyId = 0 |
| 206 | if "layerDependencies" in load: |
| 207 | index.layerDependencies = {} |
| 208 | for layerBranchId in index.layerBranches: |
| 209 | branchName = index.layerBranches[layerBranchId].branch.name |
| 210 | collection = index.layerBranches[layerBranchId].collection |
| 211 | |
| 212 | def add_dependency(layerDependencyId, index, deps, required): |
| 213 | try: |
| 214 | depDict = bb.utils.explode_dep_versions2(deps) |
| 215 | except bb.utils.VersionStringException as vse: |
| 216 | bb.fatal('Error parsing LAYERDEPENDS_%s: %s' % (c, str(vse))) |
| 217 | |
| 218 | for dep, oplist in list(depDict.items()): |
| 219 | # We need to search ourselves, so use the _ version... |
| 220 | depLayerBranch = index.find_collection(dep, branches=[branchName]) |
| 221 | if not depLayerBranch: |
| 222 | # Missing dependency?! |
| 223 | logger.error('Missing dependency %s (%s)' % (dep, branchName)) |
| 224 | continue |
| 225 | |
| 226 | # We assume that the oplist matches... |
| 227 | layerDependencyId += 1 |
| 228 | layerDependency = layerindexlib.LayerDependency(index, None) |
| 229 | layerDependency.define_data(id=layerDependencyId, |
| 230 | required=required, layerbranch=layerBranchId, |
| 231 | dependency=depLayerBranch.layer_id) |
| 232 | |
| 233 | logger.debug(1, '%s requires %s' % (layerDependency.layer.name, layerDependency.dependency.name)) |
| 234 | index.add_element("layerDependencies", [layerDependency]) |
| 235 | |
| 236 | return layerDependencyId |
| 237 | |
| 238 | deps = d.getVar("LAYERDEPENDS_%s" % collection) |
| 239 | if deps: |
| 240 | layerDependencyId = add_dependency(layerDependencyId, index, deps, True) |
| 241 | |
| 242 | deps = d.getVar("LAYERRECOMMENDS_%s" % collection) |
| 243 | if deps: |
| 244 | layerDependencyId = add_dependency(layerDependencyId, index, deps, False) |
| 245 | |
| 246 | # Need to load recipes here (requires cooker access) |
| 247 | recipeId = 0 |
| 248 | ## TODO: NOT IMPLEMENTED |
| 249 | # The code following this is an example of what needs to be |
| 250 | # implemented. However, it does not work as-is. |
| 251 | if False and 'recipes' in load: |
| 252 | index.recipes = {} |
| 253 | |
| 254 | ret = self.ui_module.main(self.server_connection.connection, self.server_connection.events, config_params) |
| 255 | |
| 256 | all_versions = self._run_command('allProviders') |
| 257 | |
| 258 | all_versions_list = defaultdict(list, all_versions) |
| 259 | for pn in all_versions_list: |
| 260 | for ((pe, pv, pr), fpath) in all_versions_list[pn]: |
| 261 | realfn = bb.cache.virtualfn2realfn(fpath) |
| 262 | |
| 263 | filepath = os.path.dirname(realfn[0]) |
| 264 | filename = os.path.basename(realfn[0]) |
| 265 | |
| 266 | # This is all HORRIBLY slow, and likely unnecessary |
| 267 | #dscon = self._run_command('parseRecipeFile', fpath, False, []) |
| 268 | #connector = myDataStoreConnector(self, dscon.dsindex) |
| 269 | #recipe_data = bb.data.init() |
| 270 | #recipe_data.setVar('_remote_data', connector) |
| 271 | |
| 272 | #summary = recipe_data.getVar('SUMMARY') |
| 273 | #description = recipe_data.getVar('DESCRIPTION') |
| 274 | #section = recipe_data.getVar('SECTION') |
| 275 | #license = recipe_data.getVar('LICENSE') |
| 276 | #homepage = recipe_data.getVar('HOMEPAGE') |
| 277 | #bugtracker = recipe_data.getVar('BUGTRACKER') |
| 278 | #provides = recipe_data.getVar('PROVIDES') |
| 279 | |
| 280 | layer = bb.utils.get_file_layer(realfn[0], self.config_data) |
| 281 | |
| 282 | depBranchId = collection_layerbranch[layer] |
| 283 | |
| 284 | recipeId += 1 |
| 285 | recipe = layerindexlib.Recipe(index, None) |
| 286 | recipe.define_data(id=recipeId, |
| 287 | filename=filename, filepath=filepath, |
| 288 | pn=pn, pv=pv, |
| 289 | summary=pn, description=pn, section='?', |
| 290 | license='?', homepage='?', bugtracker='?', |
| 291 | provides='?', bbclassextend='?', inherits='?', |
| 292 | blacklisted='?', layerbranch=depBranchId) |
| 293 | |
| 294 | index = addElement("recipes", [recipe], index) |
| 295 | |
| 296 | # ("machines", layerindexlib.Machine) |
| 297 | machineId = 0 |
| 298 | if 'machines' in load: |
| 299 | index.machines = {} |
| 300 | |
| 301 | for layerBranchId in index.layerBranches: |
| 302 | # load_bblayers uses the description to cache the actual path... |
| 303 | machine_path = index.layerBranches[layerBranchId].layer.description |
| 304 | machine_path = os.path.join(machine_path, 'conf/machine') |
| 305 | if os.path.isdir(machine_path): |
| 306 | for (dirpath, _, filenames) in os.walk(machine_path): |
| 307 | # Ignore subdirs... |
| 308 | if not dirpath.endswith('conf/machine'): |
| 309 | continue |
| 310 | for fname in filenames: |
| 311 | if fname.endswith('.conf'): |
| 312 | machineId += 1 |
| 313 | machine = layerindexlib.Machine(index, None) |
| 314 | machine.define_data(id=machineId, name=fname[:-5], |
| 315 | description=fname[:-5], |
| 316 | layerbranch=index.layerBranches[layerBranchId]) |
| 317 | |
| 318 | index.add_element("machines", [machine]) |
| 319 | |
| 320 | # ("distros", layerindexlib.Distro) |
| 321 | distroId = 0 |
| 322 | if 'distros' in load: |
| 323 | index.distros = {} |
| 324 | |
| 325 | for layerBranchId in index.layerBranches: |
| 326 | # load_bblayers uses the description to cache the actual path... |
| 327 | distro_path = index.layerBranches[layerBranchId].layer.description |
| 328 | distro_path = os.path.join(distro_path, 'conf/distro') |
| 329 | if os.path.isdir(distro_path): |
| 330 | for (dirpath, _, filenames) in os.walk(distro_path): |
| 331 | # Ignore subdirs... |
| 332 | if not dirpath.endswith('conf/distro'): |
| 333 | continue |
| 334 | for fname in filenames: |
| 335 | if fname.endswith('.conf'): |
| 336 | distroId += 1 |
| 337 | distro = layerindexlib.Distro(index, None) |
| 338 | distro.define_data(id=distroId, name=fname[:-5], |
| 339 | description=fname[:-5], |
| 340 | layerbranch=index.layerBranches[layerBranchId]) |
| 341 | |
| 342 | index.add_element("distros", [distro]) |
| 343 | |
| 344 | return index |