Skip to content

Commit 562f995

Browse files
authored
Merge pull request #436 from jkloetzke/out-of-tree-builds
Add out-of-tree build support
2 parents f3dc501 + 121337b commit 562f995

File tree

17 files changed

+254
-16
lines changed

17 files changed

+254
-16
lines changed

contrib/bash-completion/bob

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ __bob_complete_dir()
2929
compgen -d -P "$2" -S / -- "$1" ) )
3030
}
3131

32-
__bob_commands="build dev clean graph help jenkins ls project status query-scm \
33-
query-recipe query-path query-meta show"
32+
__bob_commands="build dev clean graph help init jenkins ls project status \
33+
query-scm query-recipe query-path query-meta show"
3434

3535
# Complete a Bob path
3636
#
@@ -162,6 +162,18 @@ __bob_ls()
162162
__bob_complete_path "-a --all -c -D -d --direct --no-sandbox -o --origin -p --prefixed -r --recursive --sandbox -u --unsorted"
163163
}
164164

165+
__bob_init()
166+
{
167+
case "$cur" in
168+
-*)
169+
__bob_complete_words "-h --help"
170+
;;
171+
*)
172+
__bob_complete_dir "$cur"
173+
;;
174+
esac
175+
}
176+
165177
__bob_jenkins_add()
166178
{
167179
sandbox="--sandbox"

doc/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ def __getattr__(cls, name):
260260
('manpages/bob-clean', 'bob-clean', 'Delete unused src/build/dist paths of release builds', ['Jan Klötzke'], 1),
261261
('manpages/bob-dev', 'bob-dev', 'Bob develop mode build', ['Jan Klötzke'], 1),
262262
('manpages/bob-graph', 'bob-graph', 'Generate dependency graph', ['Ralf Hubert'], 1),
263+
('manpages/bob-init', 'bob-init', 'Initialize out-of-source build tree', ['Jan Klötzke'], 1),
263264
('manpages/bob-jenkins', 'bob-jenkins', 'Configure Jenkins server', ['Jan Klötzke'], 1),
264265
('manpages/bob-ls', 'bob-ls', 'List package hierarchy', ['Jan Klötzke'], 1),
265266
('manpages/bobpaths', 'bobpaths', 'Specifying paths to Bob packages', ['Jan Klötzke'], 7),

doc/manpages/bob-build-dev.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
Build tree location
2+
-------------------
3+
4+
The build can be done directly in the project root directory or in a separate
5+
directory. To build outside of the project directory the build-tree must first
6+
be initialized with :ref:`bob init <manpage-bob-init>`. Any number of build
7+
trees may refer to the same project. Inside the external build-tree there may
8+
be a dedicated ``default.yaml``, overriding settings from the project.
9+
110
Options
211
-------
312

@@ -255,3 +264,4 @@ See also
255264
--------
256265

257266
:ref:`bobpaths(7) <manpage-bobpaths>` :ref:`bob-status(1) <manpage-bob-status>`
267+
:ref:`bob-init(1) <manpage-bob-init>`

doc/manpages/bob-init.rst

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
.. _manpage-bob-init:
2+
3+
bob-init
4+
========
5+
6+
.. only:: not man
7+
8+
Name
9+
----
10+
11+
bob-init - Initialize out-of-source build tree
12+
13+
Synopsis
14+
--------
15+
16+
::
17+
18+
bob init [-h] PROJECT [BUILD]
19+
20+
21+
Description
22+
-----------
23+
24+
Setup a build directory that builds the project located at ``PROJECT``. By
25+
default the current directory is initialized. Optionally the build directory
26+
can be specified as 2nd parameter ``BUILD``. The directories to the build
27+
directory will be created if they do not exist yet.
28+
29+
See also
30+
--------
31+
32+
:ref:`bob-build(1) <manpage-build>` :ref:`bob-dev(1) <manpage-dev>`

doc/manpages/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Contents:
1111
bob-clean
1212
bob-dev
1313
bob-graph
14+
bob-init
1415
bob-jenkins
1516
bob-ls
1617
bobpaths

doc/manual/extending.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,11 @@ internal and might change without notice.
5656
.. autoclass:: bob.input.PluginState()
5757
:members:
5858

59+
.. autoclass:: bob.input.RecipeSet()
60+
:members:
61+
5962
.. autoclass:: bob.input.Recipe()
60-
:members: getName, getPackageName, isRoot
63+
:members:
6164

6265
.. autoclass:: bob.input.Package()
6366
:members:

doc/tutorial/compile.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,20 @@ and test the whole QEMU image. The choice is yours.
169169
before moving on. Otherwise you risk that your changes are wiped out if Bob
170170
determines that a clean build is needed (e.g. due to recipe changes).
171171

172+
Out of tree builds
173+
==================
174+
175+
The :ref:`manpage-dev` and :ref:`manpage-build` commands do not need to be
176+
executed in the project directory directly. It is also possible to initialize
177+
an external build directory with :ref:`manpage-bob-init`::
178+
179+
$ bob init . /path/to/build/directory
180+
$ cd /path/to/build/directory
181+
$ bob dev vexpress
182+
183+
Each build directory can have its own :ref:`configuration-config-usr` that
184+
overrides the defaults of the project.
185+
172186
Query SCM status
173187
================
174188

pym/bob/cmds/misc.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
# SPDX-License-Identifier: GPL-3.0-or-later
77

88
from ..input import RecipeSet
9-
from ..errors import BuildError
9+
from ..errors import ParseError, BuildError
1010
from ..utils import processDefines
1111
import argparse
1212
import codecs
1313
import sys
14+
import os, os.path
1415

1516
try:
1617
# test if stdout can handle box drawing characters
@@ -268,3 +269,42 @@ def doQueryRecipe(argv, bobRoot):
268269

269270
for fn in package.getRecipe().getSources():
270271
print(fn)
272+
273+
def doInit(argv, bobRoot):
274+
parser = argparse.ArgumentParser(prog="bob init",
275+
formatter_class=argparse.RawDescriptionHelpFormatter,
276+
description="""Initialize out-of-source build tree.
277+
278+
Create a Bob build tree in the current directory or at the given BUILD
279+
directory. The recipes, classes, plugins and all other files are taken from the
280+
project root directory at PROJECT.
281+
""")
282+
parser.add_argument('project', metavar="PROJECT",
283+
help="Project root directory")
284+
parser.add_argument('build', nargs='?', metavar="BUILD", default=".",
285+
help="Build directory (default: .)")
286+
287+
args = parser.parse_args(argv)
288+
289+
recipesDir = os.path.join(args.project, "recipes")
290+
if not os.path.isdir(recipesDir):
291+
raise ParseError("No recipes directory found in " + recipesDir)
292+
293+
try:
294+
os.makedirs(args.build, exist_ok=True)
295+
if os.path.samefile(args.project, args.build):
296+
print("The project directory does not need to be initialized.",
297+
file=sys.stderr)
298+
return
299+
except OSError as e:
300+
raise ParseError("Error creating build directory: " + str(e))
301+
302+
projectLink = os.path.join(args.build, ".bob-project")
303+
if os.path.exists(projectLink):
304+
raise ParseError("Build tree already initialized!")
305+
306+
try:
307+
with open(projectLink, "w") as f:
308+
f.write(os.path.abspath(args.project))
309+
except OSError as e:
310+
raise ParseError("Cannot create project link: " + str(e))

pym/bob/input.py

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2214,6 +2214,15 @@ def __resolveClassesOrder(self, cls, stack, visited, isRecipe=False):
22142214
return ret
22152215

22162216
def getLayer(self):
2217+
"""Get layer to which this recipe belongs.
2218+
2219+
Returns a list of the layer hierarchy. The root layer is represented
2220+
by an empty list. If the recipe belongs to a nested layer the layers
2221+
are named from top to bottom. Example:
2222+
``layers/foo/layers/bar/recipes/baz.yaml`` -> ``['foo', 'bar']``.
2223+
2224+
:rtype: List[str]
2225+
"""
22172226
return self.__layer
22182227

22192228
def resolveClasses(self, rootEnv):
@@ -2359,6 +2368,7 @@ def coDet(r):
23592368
self.__root = rootEnv.evaluate(self.__root, "root")
23602369

23612370
def getRecipeSet(self):
2371+
"""Get the :class:`RecipeSet` to which the recipe belongs"""
23622372
return self.__recipeSet
23632373

23642374
def getSources(self):
@@ -2388,15 +2398,21 @@ def isRoot(self):
23882398
return self.__root == True
23892399

23902400
def isRelocatable(self):
2391-
"""Returns True if the packages of this recipe are relocatable."""
2401+
"""Returns True if the packages of this recipe are relocatable.
2402+
2403+
:meta private:
2404+
"""
23922405
return self.__relocatable
23932406

23942407
def isShared(self):
23952408
return self.__shared
23962409

23972410
def jobServer(self):
2398-
"""Returns True if the jobserver should be used to schedule
2399-
builds for this recipe."""
2411+
"""Returns True if the jobserver should be used to schedule builds for
2412+
this recipe.
2413+
2414+
:meta private:
2415+
"""
24002416
return self.__jobServer
24012417

24022418
def prepare(self, inputEnv, sandboxEnabled, inputStates, inputSandbox=None,
@@ -2972,6 +2988,10 @@ def validate(self, data):
29722988
raise schema.SchemaError(None, "Mount entry must be a string or a two/three items list!")
29732989

29742990
class RecipeSet:
2991+
"""The RecipeSet corresponds to the project root directory.
2992+
2993+
It holds global information about the project.
2994+
"""
29752995

29762996
BUILD_DEV_SCHEMA = schema.Schema(
29772997
{
@@ -3402,12 +3422,20 @@ def getProjectGenerators(self):
34023422
return self.__projectGenerators
34033423

34043424
def envWhiteList(self):
3425+
"""The set of all white listed environment variables
3426+
3427+
:rtype: Set[str]
3428+
"""
34053429
return set(self.__whiteList)
34063430

34073431
def archiveSpec(self):
34083432
return self.__archive
34093433

34103434
def defaultEnv(self):
3435+
"""The default environment that each root recipe inherits
3436+
3437+
:rtype: Mapping[str, str]
3438+
"""
34113439
return self.__defaultEnv
34123440

34133441
def scmDefaults(self):
@@ -3457,15 +3485,24 @@ def loadYaml(self, path, schema, default={}, preValidate=lambda x: None):
34573485
return schema[0].validate(default)
34583486

34593487
def parse(self, envOverrides={}, platform=getPlatformString()):
3460-
if not os.path.isdir("recipes"):
3461-
raise ParseError("No recipes directory found.")
3488+
recipesRoot = ""
3489+
if os.path.isfile(".bob-project"):
3490+
try:
3491+
with open(".bob-project") as f:
3492+
recipesRoot = f.read()
3493+
except OSError as e:
3494+
raise ParseError("Broken project link: " + str(e))
3495+
recipesDir = os.path.join(recipesRoot, "recipes")
3496+
if not os.path.isdir(recipesDir):
3497+
raise ParseError("No recipes directory found in " + recipesDir)
34623498
self.__cache.open()
34633499
try:
3464-
self.__parse(envOverrides, platform)
3500+
self.__parse(envOverrides, platform, recipesRoot)
34653501
finally:
34663502
self.__cache.close()
3503+
self.__projectRoot = recipesRoot or os.getcwd()
34673504

3468-
def __parse(self, envOverrides, platform):
3505+
def __parse(self, envOverrides, platform, recipesRoot=""):
34693506
self.__pluginPropDeps = b''
34703507
self.__pluginSettingsDeps = b''
34713508
self.__createSchemas()
@@ -3477,7 +3514,11 @@ def __parse(self, envOverrides, platform):
34773514
os.path.join(os.path.expanduser("~"), '.config')), 'bob', 'default.yaml'), True)
34783515

34793516
# Begin with root layer
3480-
self.__parseLayer([], "9999")
3517+
self.__parseLayer([], "9999", recipesRoot)
3518+
3519+
# Out-of-tree builds may have a dedicated default.yaml
3520+
if recipesRoot:
3521+
self.__parseUserConfig("default.yaml", True)
34813522

34823523
# config files overrule everything else
34833524
for c in self.__configFiles:
@@ -3520,8 +3561,8 @@ def __parse(self, envOverrides, platform):
35203561
self.__rootRecipe = Recipe.createVirtualRoot(self, sorted(filteredRoots), self.__properties)
35213562
self.__addRecipe(self.__rootRecipe)
35223563

3523-
def __parseLayer(self, layer, maxVer):
3524-
rootDir = os.path.join("", *(os.path.join("layers", l) for l in layer))
3564+
def __parseLayer(self, layer, maxVer, recipesRoot):
3565+
rootDir = os.path.join(recipesRoot, *(os.path.join("layers", l) for l in layer))
35253566
if not os.path.isdir(rootDir or "."):
35263567
raise ParseError("Layer '{}' does not exist!".format("/".join(layer)))
35273568

@@ -3562,7 +3603,7 @@ def preValidate(data):
35623603
# First parse any sub-layers. Their settings have a lower precedence
35633604
# and may be overwritten by higher layers.
35643605
for l in config.get("layers", []):
3565-
self.__parseLayer(layer + [l], maxVer)
3606+
self.__parseLayer(layer + [l], maxVer, recipesRoot)
35663607

35673608
# Load plugins and re-create schemas as new keys may have been added
35683609
self.__loadPlugins(rootDir, layer, config.get("plugins", []))
@@ -3831,6 +3872,15 @@ def sandboxFingerprints(self):
38313872
self.__sandboxFingerprints = self.getPolicy("sandboxFingerprints")
38323873
return self.__sandboxFingerprints
38333874

3875+
def getProjectRoot(self):
3876+
"""Get project root directory.
3877+
3878+
The project root is where the recipes, classes and layers are located.
3879+
In case of out-of-tree builds it will be distinct from the build
3880+
directory.
3881+
"""
3882+
return self.__projectRoot
3883+
38343884

38353885
class YamlCache:
38363886
def __if_expression_constructor(loader, node):

pym/bob/scripts.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ def __help(*args, **kwargs):
4444
doHelp(availableCommands.keys(), *args, **kwargs)
4545
return 0
4646

47+
def __init(*args, **kwargs):
48+
from .cmds.misc import doInit
49+
doInit(*args, **kwargs)
50+
return 0
51+
4752
def __jenkins(*args, **kwargs):
4853
from .cmds.jenkins import doJenkins
4954
doJenkins(*args, **kwargs)
@@ -110,6 +115,7 @@ def __invoke(*args, **kwargs):
110115
"clean" : ('hl', __clean, "Delete unused src/build/dist paths of release builds"),
111116
"graph" : ('hl', __graph, "Make a interactive dependency graph"),
112117
"help" : ('hl', __help, "Display help information about command"),
118+
"init" : ('hl', __init, "Initialize build tree"),
113119
"jenkins" : ('hl', __jenkins, "Configure Jenkins server"),
114120
"ls" : ('hl', __ls, "List package hierarchy"),
115121
"project" : ('hl', __project, "Create project files"),

0 commit comments

Comments
 (0)