blob: 87df951dc130406db2960aec91aa81ea6d57dc99 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001#!/usr/bin/env python
2
3# OpenEmbedded Development tool
4#
5# Copyright (C) 2014-2015 Intel Corporation
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20import sys
21import os
22import argparse
23import glob
24import re
25import ConfigParser
26import subprocess
27import logging
28
29basepath = ''
30workspace = {}
31config = None
32context = None
33
34
35scripts_path = os.path.dirname(os.path.realpath(__file__))
36lib_path = scripts_path + '/lib'
37sys.path = sys.path + [lib_path]
38from devtool import DevtoolError, setup_tinfoil
39import scriptutils
40logger = scriptutils.logger_create('devtool')
41
42plugins = []
43
44
45class ConfigHandler(object):
46 config_file = ''
47 config_obj = None
48 init_path = ''
49 workspace_path = ''
50
51 def __init__(self, filename):
52 self.config_file = filename
53 self.config_obj = ConfigParser.SafeConfigParser()
54
55 def get(self, section, option, default=None):
56 try:
57 ret = self.config_obj.get(section, option)
58 except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
59 if default != None:
60 ret = default
61 else:
62 raise
63 return ret
64
65 def read(self):
66 if os.path.exists(self.config_file):
67 self.config_obj.read(self.config_file)
68
69 if self.config_obj.has_option('General', 'init_path'):
70 pth = self.get('General', 'init_path')
71 self.init_path = os.path.join(basepath, pth)
72 if not os.path.exists(self.init_path):
73 logger.error('init_path %s specified in config file cannot be found' % pth)
74 return False
75 else:
76 self.config_obj.add_section('General')
77
78 self.workspace_path = self.get('General', 'workspace_path', os.path.join(basepath, 'workspace'))
79 return True
80
81
82 def write(self):
83 logger.debug('writing to config file %s' % self.config_file)
84 self.config_obj.set('General', 'workspace_path', self.workspace_path)
85 with open(self.config_file, 'w') as f:
86 self.config_obj.write(f)
87
88class Context:
89 def __init__(self, **kwargs):
90 self.__dict__.update(kwargs)
91
92
93def read_workspace():
94 global workspace
95 workspace = {}
96 if not os.path.exists(os.path.join(config.workspace_path, 'conf', 'layer.conf')):
97 if context.fixed_setup:
98 logger.error("workspace layer not set up")
99 sys.exit(1)
100 else:
101 logger.info('Creating workspace layer in %s' % config.workspace_path)
102 _create_workspace(config.workspace_path, config, basepath)
103 if not context.fixed_setup:
104 _enable_workspace_layer(config.workspace_path, config, basepath)
105
106 logger.debug('Reading workspace in %s' % config.workspace_path)
107 externalsrc_re = re.compile(r'^EXTERNALSRC(_pn-([^ =]+))? *= *"([^"]*)"$')
108 for fn in glob.glob(os.path.join(config.workspace_path, 'appends', '*.bbappend')):
109 with open(fn, 'r') as f:
110 for line in f:
111 res = externalsrc_re.match(line.rstrip())
112 if res:
113 pn = res.group(2) or os.path.splitext(os.path.basename(fn))[0].split('_')[0]
114 workspace[pn] = {'srctree': res.group(3),
115 'bbappend': fn}
116
117def create_workspace(args, config, basepath, workspace):
118 if args.layerpath:
119 workspacedir = os.path.abspath(args.layerpath)
120 else:
121 workspacedir = os.path.abspath(os.path.join(basepath, 'workspace'))
122 _create_workspace(workspacedir, config, basepath)
123 if not args.create_only:
124 _enable_workspace_layer(workspacedir, config, basepath)
125
126def _create_workspace(workspacedir, config, basepath):
127 import bb
128
129 confdir = os.path.join(workspacedir, 'conf')
130 if os.path.exists(os.path.join(confdir, 'layer.conf')):
131 logger.info('Specified workspace already set up, leaving as-is')
132 else:
133 # Add a config file
134 bb.utils.mkdirhier(confdir)
135 with open(os.path.join(confdir, 'layer.conf'), 'w') as f:
136 f.write('# ### workspace layer auto-generated by devtool ###\n')
137 f.write('BBPATH =. "$' + '{LAYERDIR}:"\n')
138 f.write('BBFILES += "$' + '{LAYERDIR}/recipes/*/*.bb \\\n')
139 f.write(' $' + '{LAYERDIR}/appends/*.bbappend"\n')
140 f.write('BBFILE_COLLECTIONS += "workspacelayer"\n')
141 f.write('BBFILE_PATTERN_workspacelayer = "^$' + '{LAYERDIR}/"\n')
142 f.write('BBFILE_PATTERN_IGNORE_EMPTY_workspacelayer = "1"\n')
143 f.write('BBFILE_PRIORITY_workspacelayer = "99"\n')
144 # Add a README file
145 with open(os.path.join(workspacedir, 'README'), 'w') as f:
146 f.write('This layer was created by the OpenEmbedded devtool utility in order to\n')
147 f.write('contain recipes and bbappends. In most instances you should use the\n')
148 f.write('devtool utility to manage files within it rather than modifying files\n')
149 f.write('directly (although recipes added with "devtool add" will often need\n')
150 f.write('direct modification.)\n')
151 f.write('\nIf you no longer need to use devtool you can remove the path to this\n')
152 f.write('workspace layer from your conf/bblayers.conf file (and then delete the\n')
153 f.write('layer, if you wish).\n')
154
155def _enable_workspace_layer(workspacedir, config, basepath):
156 """Ensure the workspace layer is in bblayers.conf"""
157 import bb
158 bblayers_conf = os.path.join(basepath, 'conf', 'bblayers.conf')
159 if not os.path.exists(bblayers_conf):
160 logger.error('Unable to find bblayers.conf')
161 return
162 _, added = bb.utils.edit_bblayers_conf(bblayers_conf, workspacedir, config.workspace_path)
163 if added:
164 logger.info('Enabling workspace layer in bblayers.conf')
165 if config.workspace_path != workspacedir:
166 # Update our config to point to the new location
167 config.workspace_path = workspacedir
168 config.write()
169
170
171def main():
172 global basepath
173 global config
174 global context
175
176 context = Context(fixed_setup=False)
177
178 # Default basepath
179 basepath = os.path.dirname(os.path.abspath(__file__))
180 pth = basepath
181 while pth != '' and pth != os.sep:
182 if os.path.exists(os.path.join(pth, '.devtoolbase')):
183 context.fixed_setup = True
184 basepath = pth
185 break
186 pth = os.path.dirname(pth)
187
188 parser = argparse.ArgumentParser(description="OpenEmbedded development tool",
189 add_help=False,
190 epilog="Use %(prog)s <subcommand> --help to get help on a specific command")
191 parser.add_argument('--basepath', help='Base directory of SDK / build directory')
192 parser.add_argument('--bbpath', help='Explicitly specify the BBPATH, rather than getting it from the metadata')
193 parser.add_argument('-d', '--debug', help='Enable debug output', action='store_true')
194 parser.add_argument('-q', '--quiet', help='Print only errors', action='store_true')
195 parser.add_argument('--color', choices=['auto', 'always', 'never'], default='auto', help='Colorize output (where %(metavar)s is %(choices)s)', metavar='COLOR')
196
197 global_args, unparsed_args = parser.parse_known_args()
198
199 # Help is added here rather than via add_help=True, as we don't want it to
200 # be handled by parse_known_args()
201 parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS,
202 help='show this help message and exit')
203
204 if global_args.debug:
205 logger.setLevel(logging.DEBUG)
206 elif global_args.quiet:
207 logger.setLevel(logging.ERROR)
208
209 if global_args.basepath:
210 # Override
211 basepath = global_args.basepath
212 elif not context.fixed_setup:
213 basepath = os.environ.get('BUILDDIR')
214 if not basepath:
215 logger.error("This script can only be run after initialising the build environment (e.g. by using oe-init-build-env)")
216 sys.exit(1)
217
218 logger.debug('Using basepath %s' % basepath)
219
220 config = ConfigHandler(os.path.join(basepath, 'conf', 'devtool.conf'))
221 if not config.read():
222 return -1
223
224 # We need to be in this directory or we won't be able to initialise tinfoil
225 os.chdir(basepath)
226
227 bitbake_subdir = config.get('General', 'bitbake_subdir', '')
228 if bitbake_subdir:
229 # Normally set for use within the SDK
230 logger.debug('Using bitbake subdir %s' % bitbake_subdir)
231 sys.path.insert(0, os.path.join(basepath, bitbake_subdir, 'lib'))
232 core_meta_subdir = config.get('General', 'core_meta_subdir')
233 sys.path.insert(0, os.path.join(basepath, core_meta_subdir, 'lib'))
234 else:
235 # Standard location
236 import scriptpath
237 bitbakepath = scriptpath.add_bitbake_lib_path()
238 if not bitbakepath:
239 logger.error("Unable to find bitbake by searching parent directory of this script or PATH")
240 sys.exit(1)
241 logger.debug('Using standard bitbake path %s' % bitbakepath)
242 scriptpath.add_oe_lib_path()
243
244 scriptutils.logger_setup_color(logger, global_args.color)
245
246 if global_args.bbpath is None:
247 tinfoil = setup_tinfoil(config_only=True)
248 global_args.bbpath = tinfoil.config_data.getVar('BBPATH', True)
249 else:
250 tinfoil = None
251
252 for path in [scripts_path] + global_args.bbpath.split(':'):
253 pluginpath = os.path.join(path, 'lib', 'devtool')
254 scriptutils.load_plugins(logger, plugins, pluginpath)
255
256 if tinfoil:
257 tinfoil.shutdown()
258
259 subparsers = parser.add_subparsers(dest="subparser_name", title='subcommands', metavar='<subcommand>')
260
261 if not context.fixed_setup:
262 parser_create_workspace = subparsers.add_parser('create-workspace',
263 help='Set up a workspace',
264 description='Sets up a new workspace. NOTE: other devtool subcommands will create a workspace automatically as needed, so you only need to use %(prog)s if you want to specify where the workspace should be located.')
265 parser_create_workspace.add_argument('layerpath', nargs='?', help='Path in which the workspace layer should be created')
266 parser_create_workspace.add_argument('--create-only', action="store_true", help='Only create the workspace layer, do not alter configuration')
267 parser_create_workspace.set_defaults(func=create_workspace)
268
269 for plugin in plugins:
270 if hasattr(plugin, 'register_commands'):
271 plugin.register_commands(subparsers, context)
272
273 args = parser.parse_args(unparsed_args, namespace=global_args)
274
275 if args.subparser_name != 'create-workspace':
276 read_workspace()
277
278 try:
279 ret = args.func(args, config, basepath, workspace)
280 except DevtoolError as err:
281 if str(err):
282 logger.error(str(err))
283 ret = 1
284
285 return ret
286
287
288if __name__ == "__main__":
289 try:
290 ret = main()
291 except Exception:
292 ret = 1
293 import traceback
294 traceback.print_exc(5)
295 sys.exit(ret)