Skip to content
Snippets Groups Projects
Commit 12ca9607 authored by Tristan Van Berkom's avatar Tristan Van Berkom
Browse files

Enhanced bst shell configuration and cli options

Some changes to the host-files configuration:

  o Dont require `host-files` to not be directories

    We need to specify directories to mount from `project.conf` after all.

  o Added possibility of specifying optional mounts, to avoid
    meaningless warnings where optional files don't exist on
    the host

Added --mount CLI option to `bst shell`

This allows users to explicitly mount whatever they want into the
sandbox environment for `bst shell`.

This closes issue #274
parent a3f1a8ef
No related branches found
No related tags found
Loading
...@@ -462,13 +462,16 @@ def show(app, elements, deps, except_, order, format, downloadable): ...@@ -462,13 +462,16 @@ def show(app, elements, deps, except_, order, format, downloadable):
@click.option('--sysroot', '-s', default=None, @click.option('--sysroot', '-s', default=None,
type=click.Path(exists=True, file_okay=False, readable=True), type=click.Path(exists=True, file_okay=False, readable=True),
help="An existing sysroot") help="An existing sysroot")
@click.option('--mount', type=click.Tuple([click.Path(exists=True), str]), multiple=True,
metavar='HOSTPATH PATH',
help="Mount a file or directory into the sandbox")
@click.option('--isolate', is_flag=True, default=False, @click.option('--isolate', is_flag=True, default=False,
help='Create an isolated build sandbox') help='Create an isolated build sandbox')
@click.argument('element', @click.argument('element',
type=click.Path(dir_okay=False, readable=True)) type=click.Path(dir_okay=False, readable=True))
@click.argument('command', type=click.STRING, nargs=-1) @click.argument('command', type=click.STRING, nargs=-1)
@click.pass_obj @click.pass_obj
def shell(app, element, sysroot, isolate, build, command): def shell(app, element, sysroot, mount, isolate, build, command):
"""Run a command in the target element's sandbox environment """Run a command in the target element's sandbox environment
This will stage a temporary sysroot for running the target This will stage a temporary sysroot for running the target
...@@ -486,6 +489,7 @@ def shell(app, element, sysroot, isolate, build, command): ...@@ -486,6 +489,7 @@ def shell(app, element, sysroot, isolate, build, command):
to run an interactive shell. to run an interactive shell.
""" """
from ..element import Scope from ..element import Scope
from .._project import HostMount
if build: if build:
scope = Scope.BUILD scope = Scope.BUILD
else: else:
...@@ -509,9 +513,14 @@ def shell(app, element, sysroot, isolate, build, command): ...@@ -509,9 +513,14 @@ def shell(app, element, sysroot, isolate, build, command):
click.echo("Try building them first", err=True) click.echo("Try building them first", err=True)
sys.exit(-1) sys.exit(-1)
mounts = [
HostMount(path, host_path)
for host_path, path in mount
]
try: try:
element = app.pipeline.targets[0] element = app.pipeline.targets[0]
exitcode = app.shell(element, scope, sysroot, isolate=isolate, command=command) exitcode = app.shell(element, scope, sysroot, mounts=mounts, isolate=isolate, command=command)
sys.exit(exitcode) sys.exit(exitcode)
except BstError as e: except BstError as e:
click.echo("", err=True) click.echo("", err=True)
......
...@@ -365,7 +365,7 @@ class App(): ...@@ -365,7 +365,7 @@ class App():
queue.failed_elements.remove(element) queue.failed_elements.remove(element)
queue.enqueue([element]) queue.enqueue([element])
def shell(self, element, scope, directory, isolate=False, command=None): def shell(self, element, scope, directory, *, mounts=None, isolate=False, command=None):
_, key, dim = element._get_full_display_key() _, key, dim = element._get_full_display_key()
element_name = element._get_full_name() element_name = element._get_full_name()
...@@ -380,7 +380,7 @@ class App(): ...@@ -380,7 +380,7 @@ class App():
else: else:
prompt = '[{}@{}:${{PWD}}]$ '.format(key, element_name) prompt = '[{}@{}:${{PWD}}]$ '.format(key, element_name)
return element._shell(scope, directory, isolate=isolate, prompt=prompt, command=command) return element._shell(scope, directory, mounts=mounts, isolate=isolate, prompt=prompt, command=command)
def tick(self, elapsed): def tick(self, elapsed):
self.maybe_render_status() self.maybe_render_status()
......
...@@ -39,12 +39,29 @@ from ._sourcefactory import SourceFactory ...@@ -39,12 +39,29 @@ from ._sourcefactory import SourceFactory
# This version is bumped whenever enhancements are made # This version is bumped whenever enhancements are made
# to the `project.conf` format or the core element format. # to the `project.conf` format or the core element format.
# #
BST_FORMAT_VERSION = 2 BST_FORMAT_VERSION = 3
# The separator we use for user specified aliases # The separator we use for user specified aliases
_ALIAS_SEPARATOR = ':' _ALIAS_SEPARATOR = ':'
# HostMount()
#
# A simple object describing the behavior of
# a host mount.
#
class HostMount():
def __init__(self, path, host_path=None, optional=False):
self.path = path # Path inside the sandbox
self.host_path = host_path # Path on the host
self.optional = optional # Optional mounts do not incur warnings or errors
if self.host_path is None:
self.host_path = self.path
# Project() # Project()
# #
# The Project Configuration # The Project Configuration
...@@ -82,7 +99,7 @@ class Project(): ...@@ -82,7 +99,7 @@ class Project():
# Shell options # Shell options
self._shell_command = [] # The default interactive shell command self._shell_command = [] # The default interactive shell command
self._shell_env_inherit = [] # Environment vars to inherit when non-isolated self._shell_env_inherit = [] # Environment vars to inherit when non-isolated
self._shell_host_files = {} # Mapping of sandbox paths to host paths self._shell_host_files = [] # A list of HostMount objects
profile_start(Topics.LOAD_PROJECT, self.directory.replace(os.sep, '-')) profile_start(Topics.LOAD_PROJECT, self.directory.replace(os.sep, '-'))
self._load() self._load()
...@@ -286,14 +303,20 @@ class Project(): ...@@ -286,14 +303,20 @@ class Project():
host_files = _yaml.node_get(shell_options, list, 'host-files', default_value=[]) host_files = _yaml.node_get(shell_options, list, 'host-files', default_value=[])
for host_file in host_files: for host_file in host_files:
if isinstance(host_file, str): if isinstance(host_file, str):
self._shell_host_files[host_file] = host_file mount = HostMount(host_file)
else: else:
# Some validation
index = host_files.index(host_file) index = host_files.index(host_file)
host_file_desc = _yaml.node_get(shell_options, Mapping, 'host-files', indices=[index]) host_file_desc = _yaml.node_get(shell_options, Mapping, 'host-files', indices=[index])
_yaml.node_validate(host_file_desc, ['host', 'sandbox']) _yaml.node_validate(host_file_desc, ['path', 'host_path', 'optional'])
host_path = _yaml.node_get(host_file_desc, str, 'host')
sandbox_path = _yaml.node_get(host_file_desc, str, 'sandbox') # Parse the host mount
self._shell_host_files[sandbox_path] = host_path path = _yaml.node_get(host_file_desc, str, 'path')
host_path = _yaml.node_get(host_file_desc, str, 'host_path', default_value='') or None
optional = _yaml.node_get(host_file_desc, bool, 'optional', default_value=False)
mount = HostMount(path, host_path, optional)
self._shell_host_files.append(mount)
# _store_origin() # _store_origin()
# #
......
...@@ -1404,6 +1404,7 @@ class Element(Plugin): ...@@ -1404,6 +1404,7 @@ class Element(Plugin):
# Args: # Args:
# scope (Scope): Either BUILD or RUN scopes are valid, or None # scope (Scope): Either BUILD or RUN scopes are valid, or None
# directory (str): A directory to an existing sandbox, or None # directory (str): A directory to an existing sandbox, or None
# mounts (list): A list of (str, str) tuples, representing host/target paths to mount
# isolate (bool): Whether to isolate the environment like we do in builds # isolate (bool): Whether to isolate the environment like we do in builds
# prompt (str): A suitable prompt string for PS1 # prompt (str): A suitable prompt string for PS1
# command (list): An argv to launch in the sandbox # command (list): An argv to launch in the sandbox
...@@ -1411,7 +1412,7 @@ class Element(Plugin): ...@@ -1411,7 +1412,7 @@ class Element(Plugin):
# Returns: Exit code # Returns: Exit code
# #
# If directory is not specified, one will be staged using scope # If directory is not specified, one will be staged using scope
def _shell(self, scope=None, directory=None, isolate=False, prompt=None, command=None): def _shell(self, scope=None, directory=None, *, mounts=None, isolate=False, prompt=None, command=None):
with self._prepare_sandbox(scope, directory) as sandbox: with self._prepare_sandbox(scope, directory) as sandbox:
environment = self.get_environment() environment = self.get_environment()
...@@ -1438,15 +1439,17 @@ class Element(Plugin): ...@@ -1438,15 +1439,17 @@ class Element(Plugin):
if os.environ.get(inherit) is not None: if os.environ.get(inherit) is not None:
environment[inherit] = os.environ.get(inherit) environment[inherit] = os.environ.get(inherit)
# Setup any project defined bind mounts # Setup any requested bind mounts
for target, source in _yaml.node_items(project._shell_host_files): if mounts is None:
if not os.path.exists(source): mounts = []
self.warn("Not mounting non-existing host file: {}".format(source))
elif os.path.isdir(source): for mount in project._shell_host_files + mounts:
self.warn("Not mounting directory listed as host file: {}".format(source)) if not os.path.exists(mount.host_path):
if not mount.optional:
self.warn("Not mounting non-existing host file: {}".format(mount.host_path))
else: else:
sandbox.mark_directory(target) sandbox.mark_directory(mount.path)
sandbox._set_mount_source(target, source) sandbox._set_mount_source(mount.path, mount.host_path)
if command: if command:
argv = [arg for arg in command] argv = [arg for arg in command]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment