Patrick Williams | 92b42cb | 2022-09-03 06:53:57 -0500 | [diff] [blame] | 1 | # |
| 2 | # Copyright OpenEmbedded Contributors |
| 3 | # |
| 4 | # SPDX-License-Identifier: GPL-2.0-only |
| 5 | # |
| 6 | |
| 7 | import logging |
| 8 | import os |
| 9 | import stat |
| 10 | import sys |
| 11 | import shutil |
| 12 | |
| 13 | import bb.utils |
| 14 | import bb.process |
| 15 | |
| 16 | from bblayers.common import LayerPlugin |
| 17 | |
| 18 | logger = logging.getLogger('bitbake-layers') |
| 19 | |
| 20 | sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) |
| 21 | |
| 22 | import oe.buildcfg |
| 23 | |
| 24 | def plugin_init(plugins): |
| 25 | return MakeSetupPlugin() |
| 26 | |
| 27 | class MakeSetupPlugin(LayerPlugin): |
| 28 | |
| 29 | def _get_repo_path(self, layer_path): |
| 30 | repo_path, _ = bb.process.run('git rev-parse --show-toplevel', cwd=layer_path) |
| 31 | return repo_path.strip() |
| 32 | |
| 33 | def _get_remotes(self, repo_path): |
| 34 | remotes = {} |
| 35 | remotes_list,_ = bb.process.run('git remote', cwd=repo_path) |
| 36 | for r in remotes_list.split(): |
| 37 | uri,_ = bb.process.run('git remote get-url {r}'.format(r=r), cwd=repo_path) |
| 38 | remotes[r] = {'uri':uri.strip()} |
| 39 | return remotes |
| 40 | |
| 41 | def _get_describe(self, repo_path): |
| 42 | try: |
| 43 | describe,_ = bb.process.run('git describe --tags', cwd=repo_path) |
| 44 | except bb.process.ExecutionError: |
| 45 | return "" |
| 46 | return describe.strip() |
| 47 | |
Andrew Geissler | 6aa7eec | 2023-03-03 12:41:14 -0600 | [diff] [blame^] | 48 | def _is_submodule(self, repo_path): |
| 49 | # This is slightly brittle: git does not offer a way to tell whether |
| 50 | # a given repo dir is a submodule checkout, so we need to rely on .git |
| 51 | # being a file (rather than a dir like it is in standalone checkouts). |
| 52 | # The file typically contains a gitdir pointer to elsewhere. |
| 53 | return os.path.isfile(os.path.join(repo_path,".git")) |
| 54 | |
Patrick Williams | 92b42cb | 2022-09-03 06:53:57 -0500 | [diff] [blame] | 55 | def make_repo_config(self, destdir): |
Andrew Geissler | 517393d | 2023-01-13 08:55:19 -0600 | [diff] [blame] | 56 | """ This is a helper function for the writer plugins that discovers currently configured layers. |
Patrick Williams | 92b42cb | 2022-09-03 06:53:57 -0500 | [diff] [blame] | 57 | The writers do not have to use it, but it can save a bit of work and avoid duplicated code, hence it is |
| 58 | available here. """ |
| 59 | repos = {} |
| 60 | layers = oe.buildcfg.get_layer_revisions(self.tinfoil.config_data) |
| 61 | try: |
| 62 | destdir_repo = self._get_repo_path(destdir) |
| 63 | except bb.process.ExecutionError: |
| 64 | destdir_repo = None |
| 65 | |
| 66 | for (l_path, l_name, l_branch, l_rev, l_ismodified) in layers: |
| 67 | if l_name == 'workspace': |
| 68 | continue |
| 69 | if l_ismodified: |
| 70 | logger.error("Layer {name} in {path} has uncommitted modifications or is not in a git repository.".format(name=l_name,path=l_path)) |
| 71 | return |
| 72 | repo_path = self._get_repo_path(l_path) |
Andrew Geissler | 6aa7eec | 2023-03-03 12:41:14 -0600 | [diff] [blame^] | 73 | |
| 74 | if self._is_submodule(repo_path): |
| 75 | continue |
Patrick Williams | 92b42cb | 2022-09-03 06:53:57 -0500 | [diff] [blame] | 76 | if repo_path not in repos.keys(): |
| 77 | repos[repo_path] = {'path':os.path.basename(repo_path),'git-remote':{'rev':l_rev, 'branch':l_branch, 'remotes':self._get_remotes(repo_path), 'describe':self._get_describe(repo_path)}} |
| 78 | if repo_path == destdir_repo: |
| 79 | repos[repo_path]['contains_this_file'] = True |
| 80 | if not repos[repo_path]['git-remote']['remotes'] and not repos[repo_path]['contains_this_file']: |
| 81 | logger.error("Layer repository in {path} does not have any remotes configured. Please add at least one with 'git remote add'.".format(path=repo_path)) |
| 82 | return |
| 83 | |
| 84 | top_path = os.path.commonpath([os.path.dirname(r) for r in repos.keys()]) |
| 85 | |
| 86 | repos_nopaths = {} |
| 87 | for r in repos.keys(): |
| 88 | r_nopath = os.path.basename(r) |
| 89 | repos_nopaths[r_nopath] = repos[r] |
| 90 | r_relpath = os.path.relpath(r, top_path) |
| 91 | repos_nopaths[r_nopath]['path'] = r_relpath |
| 92 | return repos_nopaths |
| 93 | |
| 94 | def do_make_setup(self, args): |
| 95 | """ Writes out a configuration file and/or a script that replicate the directory structure and revisions of the layers in a current build. """ |
| 96 | for p in self.plugins: |
| 97 | if str(p) == args.writer: |
| 98 | p.do_write(self, args) |
| 99 | |
| 100 | def register_commands(self, sp): |
| 101 | parser_setup_layers = self.add_command(sp, 'create-layers-setup', self.do_make_setup, parserecipes=False) |
| 102 | parser_setup_layers.add_argument('destdir', |
| 103 | help='Directory where to write the output\n(if it is inside one of the layers, the layer becomes a bootstrap repository and thus will be excluded from fetching).') |
| 104 | parser_setup_layers.add_argument('--output-prefix', '-o', |
| 105 | help='File name prefix for the output files, if the default (setup-layers) is undesirable.') |
| 106 | |
| 107 | self.plugins = [] |
| 108 | |
| 109 | for path in (self.tinfoil.config_data.getVar('BBPATH').split(':')): |
| 110 | pluginpath = os.path.join(path, 'lib', 'bblayers', 'setupwriters') |
| 111 | bb.utils.load_plugins(logger, self.plugins, pluginpath) |
| 112 | |
| 113 | parser_setup_layers.add_argument('--writer', '-w', choices=[str(p) for p in self.plugins], help="Choose the output format (defaults to oe-setup-layers).\n\nCurrently supported options are:\noe-setup-layers - a self-contained python script and a json config for it.\n\n", default="oe-setup-layers") |
| 114 | |
| 115 | for plugin in self.plugins: |
| 116 | if hasattr(plugin, 'register_arguments'): |
| 117 | plugin.register_arguments(parser_setup_layers) |