blob: befe94c8987d7bb175497477b4e2d661ddab699f [file] [log] [blame]
# Copyright 2015 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Utilities to work with blueprints."""
from __future__ import print_function
import os
from chromite.lib import brick_lib
from chromite.lib import workspace_lib
# Field names for specifying initial configuration.
APP_ID_FIELD = 'buildTargetId'
BRICKS_FIELD = 'bricks'
BSP_FIELD = 'bsp'
# Those packages are implicitly built for all blueprints.
# - target-os is needed to build any image.
# - target-os-dev and target-os-test are needed to build a developer friendly
# image. They should not be included in any production images.
_IMPLICIT_PACKAGES = (
'virtual/target-os',
'virtual/target-os-dev',
'virtual/target-os-test',
)
class BlueprintNotFoundError(Exception):
"""The blueprint does not exist."""
class BlueprintCreationError(Exception):
"""Blueprint creation failed."""
class Blueprint(object):
"""Encapsulates the interaction with a blueprint."""
def __init__(self, blueprint_loc, initial_config=None):
"""Instantiates a blueprint object.
Args:
blueprint_loc: blueprint locator. This can be a relative path to CWD, an
absolute path, or a relative path to the root of the workspace prefixed
with '//'.
initial_config: A dictionary of key-value pairs to seed a new blueprint
with if the specified blueprint doesn't already exist.
Raises:
BlueprintNotFoundError: No blueprint exists at |blueprint_loc| and no
|initial_config| was given to create a new one.
BlueprintCreationError: |initial_config| was specified but a file
already exists at |blueprint_loc|.
"""
self._path = (workspace_lib.LocatorToPath(blueprint_loc)
if workspace_lib.IsLocator(blueprint_loc) else blueprint_loc)
self._locator = workspace_lib.PathToLocator(self._path)
if initial_config is not None:
self._CreateBlueprintConfig(initial_config)
try:
self.config = workspace_lib.ReadConfigFile(self._path)
except IOError:
raise BlueprintNotFoundError('Blueprint %s not found.' % self._path)
@property
def path(self):
return self._path
@property
def locator(self):
return self._locator
def _CreateBlueprintConfig(self, config):
"""Create an initial blueprint config file.
Converts all brick paths in |config| into locators then saves the
configuration file to |self._path|.
Currently fails if |self._path| already exists, but could be
generalized to allow re-writing config files if needed.
Args:
config: configuration dictionary.
Raises:
BlueprintCreationError: A brick in |config| doesn't exist or an
error occurred while saving the config file.
"""
if os.path.exists(self._path):
raise BlueprintCreationError('File already exists at %s.' % self._path)
try:
# Turn brick specifications into locators. If bricks or BSPs are
# unspecified, assign default values so the config file has the proper
# structure for easy manual editing.
if config.get(BRICKS_FIELD):
config[BRICKS_FIELD] = [brick_lib.Brick(b).brick_locator
for b in config[BRICKS_FIELD]]
else:
config[BRICKS_FIELD] = []
if config.get(BSP_FIELD):
config[BSP_FIELD] = brick_lib.Brick(config[BSP_FIELD]).brick_locator
else:
config[BSP_FIELD] = None
# Create the config file.
workspace_lib.WriteConfigFile(self._path, config)
except (brick_lib.BrickNotFound, workspace_lib.ConfigFileError) as e:
raise BlueprintCreationError('Blueprint creation failed. %s' % e)
def GetBricks(self):
"""Returns the bricks field of a blueprint."""
return self.config.get(BRICKS_FIELD, [])
def GetBSP(self):
"""Returns the BSP field of a blueprint."""
return self.config.get(BSP_FIELD)
def GetAppId(self):
"""Returns the APP_ID from a blueprint."""
app_id = self.config.get(APP_ID_FIELD)
return app_id
def FriendlyName(self):
"""Returns the friendly name for this blueprint."""
return workspace_lib.LocatorToFriendlyName(self._locator)
def GetUsedBricks(self):
"""Returns the set of bricks used by this blueprint."""
brick_map = {}
for top_brick in self.GetBricks() + [self.GetBSP()]:
for b in brick_lib.Brick(top_brick).BrickStack():
brick_map[b.brick_locator] = b
return brick_map.values()
def GetPackages(self, with_implicit=True):
"""Returns the list of packages needed by this blueprint.
This includes the main packages for the bricks and the bsp of this
blueprint. We don't add the main packages of the bricks dependencies to
allow inheriting a brick without inheriting its required packages.
Args:
with_implicit: If True, include packages that are implicitly required by
the core system.
"""
packages = []
for locator in self.GetBricks() + [self.GetBSP()]:
packages.extend(brick_lib.Brick(locator).MainPackages())
if with_implicit:
packages.extend(_IMPLICIT_PACKAGES)
return packages