summaryrefslogtreecommitdiff
path: root/lib/blueprint_lib.py
blob: befe94c8987d7bb175497477b4e2d661ddab699f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# 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