add support for manifest filenames other than west.yml

Signed-off-by: Michael Zimmermann <michael.zimmermann@grandcentrix.net>
This commit is contained in:
Michael Zimmermann 2020-07-25 17:21:30 +02:00 committed by Marti Bolivar
parent 6350ca4f86
commit 80bf5cf381
5 changed files with 104 additions and 26 deletions

View File

@ -15,10 +15,10 @@ Basic Usage
----------- -----------
West lets you manage multiple Git repositories under a single directory using a West lets you manage multiple Git repositories under a single directory using a
single file, called the *west manifest file*, or *manifest* for short. The single file, called the *west manifest file*, or *manifest* for short.
manifest file is named ``west.yml``. You use ``west init`` to set up this By default the manifest file is named ``west.yml``.
directory, then ``west update`` to fetch and/or update the repositories named You use ``west init`` to set up this directory, then ``west update`` to fetch
in the manifest. and/or update the repositories named in the manifest.
By default, west uses `upstream Zephyr's manifest file By default, west uses `upstream Zephyr's manifest file
<https://github.com/zephyrproject-rtos/zephyr/blob/master/west.yml>`_, but west <https://github.com/zephyrproject-rtos/zephyr/blob/master/west.yml>`_, but west
@ -45,7 +45,8 @@ What just happened:
creating working trees in the installation directory ``zephyrproject``. creating working trees in the installation directory ``zephyrproject``.
Use ``west init -m`` to specify another manifest repository. Use ``--mr`` to Use ``west init -m`` to specify another manifest repository. Use ``--mr`` to
use a revision other than ``master``. use a revision other than ``master``. Use ``--mf`` to use a manifest file other
than ``west.yml``.
Additional Commands Additional Commands
------------------- -------------------

View File

@ -181,7 +181,7 @@ class WestApp:
list(self.mle.args))) list(self.mle.args)))
elif isinst(FileNotFoundError): elif isinst(FileNotFoundError):
# This should ordinarily only happen when the top # This should ordinarily only happen when the top
# level west.yml is not found. # level manifest is not found.
log.die(f"file not found: {self.mle.filename}") log.die(f"file not found: {self.mle.filename}")
elif isinst(_ManifestImportDepth): elif isinst(_ManifestImportDepth):
log.die('failed, likely due to manifest import loop') log.die('failed, likely due to manifest import loop')

View File

@ -124,7 +124,7 @@ class Init(_ProjectCommand):
1. Creates a .west directory and clones a manifest repository 1. Creates a .west directory and clones a manifest repository
from a git URL to a temporary subdirectory of .west, from a git URL to a temporary subdirectory of .west,
.west/<tmpdir>. .west/<tmpdir>.
2. Parses the manifest file, .west/<tmpdir>/west.yml. 2. Parses the manifest file, .west/<tmpdir>/<manifest.file>.
This file's contents can specify manifest.path, the location This file's contents can specify manifest.path, the location
of the manifest repository in the workspace, like so: of the manifest repository in the workspace, like so:
@ -161,6 +161,8 @@ class Init(_ProjectCommand):
parser.add_argument('--mr', '--manifest-rev', dest='manifest_rev', parser.add_argument('--mr', '--manifest-rev', dest='manifest_rev',
help='''manifest revision to check out and use; help='''manifest revision to check out and use;
cannot be combined with -l''') cannot be combined with -l''')
parser.add_argument('--mf', '--manifest-file', dest='manifest_file',
help='manifest file name to use')
parser.add_argument('-l', '--local', action='store_true', parser.add_argument('-l', '--local', action='store_true',
help='''use an existing local manifest repository help='''use an existing local manifest repository
instead of cloning one; cannot be combined with instead of cloning one; cannot be combined with
@ -212,13 +214,15 @@ class Init(_ProjectCommand):
log.die('--mr cannot be used with -l') log.die('--mr cannot be used with -l')
manifest_dir = Path(args.directory or os.getcwd()) manifest_dir = Path(args.directory or os.getcwd())
manifest_file = manifest_dir / 'west.yml' manifest_filename = args.manifest_file or 'west.yml'
manifest_file = manifest_dir / manifest_filename
topdir = manifest_dir.parent topdir = manifest_dir.parent
rel_manifest = manifest_dir.name rel_manifest = manifest_dir.name
west_dir = topdir / WEST_DIR west_dir = topdir / WEST_DIR
if not manifest_file.is_file(): if not manifest_file.is_file():
log.die(f'can\'t init: no "west.yml" found in {manifest_dir}') log.die(f'can\'t init: no {manifest_filename} found in '
f'{manifest_dir}')
log.banner('Initializing from existing manifest repository', log.banner('Initializing from existing manifest repository',
rel_manifest) rel_manifest)
@ -226,6 +230,7 @@ class Init(_ProjectCommand):
self.create(west_dir) self.create(west_dir)
os.chdir(topdir) os.chdir(topdir)
update_config('manifest', 'path', os.fspath(rel_manifest)) update_config('manifest', 'path', os.fspath(rel_manifest))
update_config('manifest', 'file', manifest_filename, topdir=topdir)
return topdir return topdir
@ -257,9 +262,11 @@ class Init(_ProjectCommand):
raise raise
# Verify the manifest file exists. # Verify the manifest file exists.
temp_manifest = tempdir / 'west.yml' temp_manifest_filename = args.manifest_file or 'west.yml'
temp_manifest = tempdir / temp_manifest_filename
if not temp_manifest.is_file(): if not temp_manifest.is_file():
log.die(f'can\'t init: no "west.yml" found in {tempdir}\n' log.die(f'can\'t init: no {temp_manifest_filename} found in '
f'{tempdir}\n'
f' Hint: check --manifest-url={manifest_url} and ' f' Hint: check --manifest-url={manifest_url} and '
f'--manifest-rev={manifest_rev}\n' f'--manifest-rev={manifest_rev}\n'
f' You may need to remove {west_dir} before retrying.') f' You may need to remove {west_dir} before retrying.')
@ -290,6 +297,8 @@ class Init(_ProjectCommand):
log.die(e) log.die(e)
log.small_banner('setting manifest.path to', manifest_path) log.small_banner('setting manifest.path to', manifest_path)
update_config('manifest', 'path', manifest_path, topdir=topdir) update_config('manifest', 'path', manifest_path, topdir=topdir)
update_config('manifest', 'file', temp_manifest_filename,
topdir=topdir)
return topdir return topdir
@ -640,15 +649,15 @@ class Update(_ProjectCommand):
def __init__(self): def __init__(self):
super().__init__( super().__init__(
'update', 'update',
'update projects described in west.yml', 'update projects described in west manifest',
textwrap.dedent('''\ textwrap.dedent('''\
Updates each project repository to the revision specified in Updates each project repository to the revision specified in
the manifest file, west.yml, as follows: the manifest file, as follows:
1. Fetch the project's remote to ensure the manifest 1. Fetch the project's remote to ensure the manifest
revision is available locally revision is available locally
2. Reset the manifest-rev branch to the revision in 2. Reset the manifest-rev branch to the revision in
west.yml the manifest
3. Check out the new manifest-rev commit as a detached HEAD 3. Check out the new manifest-rev commit as a detached HEAD
(but see "checked out branches") (but see "checked out branches")

View File

@ -17,7 +17,7 @@ import shlex
import subprocess import subprocess
import sys import sys
from typing import Any, Callable, Dict, Iterable, List, NoReturn, \ from typing import Any, Callable, Dict, Iterable, List, NoReturn, \
NamedTuple, Optional, TYPE_CHECKING, Union NamedTuple, Optional, Tuple, TYPE_CHECKING, Union
from packaging.version import parse as parse_version from packaging.version import parse as parse_version
import pykwalify.core import pykwalify.core
@ -138,7 +138,7 @@ def _west_commands_merge(wc1: List[str], wc2: List[str]) -> List[str]:
return wc1 or wc2 return wc1 or wc2
def _mpath(cp: Optional[configparser.ConfigParser] = None, def _mpath(cp: Optional[configparser.ConfigParser] = None,
topdir: Optional[PathType] = None) -> str: topdir: Optional[PathType] = None) -> Tuple[str, str]:
# Return the value of the manifest.path configuration option # Return the value of the manifest.path configuration option
# in *cp*, a ConfigParser. If not given, create a new one and # in *cp*, a ConfigParser. If not given, create a new one and
# load configuration options with the given *topdir* as west # load configuration options with the given *topdir* as west
@ -152,7 +152,10 @@ def _mpath(cp: Optional[configparser.ConfigParser] = None,
cfg.read_config(configfile=cfg.ConfigFile.LOCAL, config=cp, topdir=topdir) cfg.read_config(configfile=cfg.ConfigFile.LOCAL, config=cp, topdir=topdir)
try: try:
return cp.get('manifest', 'path') path = cp.get('manifest', 'path')
filename = cp.get('manifest', 'file', fallback=_WEST_YML)
return (path, filename)
except (configparser.NoOptionError, configparser.NoSectionError) as e: except (configparser.NoOptionError, configparser.NoSectionError) as e:
raise MalformedConfig('no "manifest.path" config option is set') from e raise MalformedConfig('no "manifest.path" config option is set') from e
@ -308,10 +311,11 @@ def manifest_path() -> str:
- `MalformedConfig` if the configuration file has no - `MalformedConfig` if the configuration file has no
``manifest.path`` key ``manifest.path`` key
- ``FileNotFoundError`` if no ``west.yml`` exists in - ``FileNotFoundError`` if no manifest file exists as determined by
``manifest.path`` ``manifest.path`` and ``manifest.file``
''' '''
ret = os.path.join(util.west_topdir(), _mpath(), _WEST_YML) (mpath, mname) = _mpath()
ret = os.path.join(util.west_topdir(), mpath, mname)
# It's kind of annoying to manually instantiate a FileNotFoundError. # It's kind of annoying to manually instantiate a FileNotFoundError.
# This seems to be the best way. # This seems to be the best way.
if not os.path.isfile(ret): if not os.path.isfile(ret):
@ -897,7 +901,7 @@ class Manifest:
- If neither *source_file* nor *topdir* is given, the file - If neither *source_file* nor *topdir* is given, the file
system is searched for *topdir*. That workspace's system is searched for *topdir*. That workspace's
``manifest.path`` configuration option is used to find ``manifest.path`` configuration option is used to find
*source_file*, ``topdir/<manifest.path>/west.yml``. *source_file*, ``topdir/<manifest.path>/<manifest.file>``.
- If only *source_file* is given, *topdir* is found - If only *source_file* is given, *topdir* is found
starting there. The directory containing *source_file* starting there. The directory containing *source_file*
@ -932,10 +936,10 @@ class Manifest:
# neither source_file nor topdir: search the filesystem # neither source_file nor topdir: search the filesystem
# for the workspace and use its manifest.path. # for the workspace and use its manifest.path.
topdir = util.west_topdir() topdir = util.west_topdir()
mpath = _mpath(topdir=topdir) (mpath, mname) = _mpath(topdir=topdir)
kwargs.update({ kwargs.update({
'topdir': topdir, 'topdir': topdir,
'source_file': os.path.join(topdir, mpath, _WEST_YML), 'source_file': os.path.join(topdir, mpath, mname),
'manifest_path': mpath 'manifest_path': mpath
}) })
else: else:
@ -960,8 +964,8 @@ class Manifest:
# Read manifest.path from topdir/.west/config, and use it # Read manifest.path from topdir/.west/config, and use it
# to locate source_file. # to locate source_file.
mpath = _mpath(topdir=topdir) (mpath, mname) = _mpath(topdir=topdir)
source_file = os.path.join(topdir, mpath, _WEST_YML) source_file = os.path.join(topdir, mpath, mname)
kwargs.update({ kwargs.update({
'source_file': source_file, 'source_file': source_file,
'manifest_path': mpath, 'manifest_path': mpath,
@ -1747,7 +1751,7 @@ class Manifest:
except FileNotFoundError: except FileNotFoundError:
# We may need to fetch a new manifest-rev, e.g. if # We may need to fetch a new manifest-rev, e.g. if
# revision is a branch that didn't used to have a # revision is a branch that didn't used to have a
# west.yml, but now does. # manifest, but now does.
content = self._importer(project, path) content = self._importer(project, path)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
# We may need a new manifest-rev, e.g. if revision is # We may need a new manifest-rev, e.g. if revision is

View File

@ -589,6 +589,70 @@ def test_init_local_missing_west_yml_failure(repos_tmpdir):
cmd(f'init -l "{zephyr_install_dir}"') cmd(f'init -l "{zephyr_install_dir}"')
def test_init_local_with_manifest_filename(repos_tmpdir):
# Test 'west init --mf -l' on a local repo
manifest = repos_tmpdir / 'repos' / 'zephyr'
workspace = repos_tmpdir / 'workspace'
zephyr_install_dir = workspace / 'zephyr'
# Do a local clone of manifest repo
clone(str(manifest), str(zephyr_install_dir))
os.rename(str(zephyr_install_dir / 'west.yml'),
str(zephyr_install_dir / 'project.yml'))
# fails because west.yml is missing
with pytest.raises(subprocess.CalledProcessError):
cmd(f'init -l "{zephyr_install_dir}"')
# create a manifest with a syntax error so we can test if it's being parsed
with open(zephyr_install_dir / 'west.yml', 'w') as f:
f.write('[')
cwd = os.getcwd()
cmd(f'init -l "{zephyr_install_dir}"')
# init with a local manifest doesn't parse the file, so let's access it
workspace.chdir()
with pytest.raises(subprocess.CalledProcessError):
cmd('list')
os.chdir(cwd)
shutil.move(workspace / '.west', workspace / '.west-syntaxerror')
# success
cmd(f'init --mf project.yml -l "{zephyr_install_dir}"')
workspace.chdir()
config.read_config()
cmd('update')
def test_init_with_manifest_filename(repos_tmpdir):
# Test 'west init --mf' on a normal repo
west_tmpdir = repos_tmpdir / 'workspace'
manifest = repos_tmpdir / 'repos' / 'zephyr'
with open(manifest / 'west.yml', 'r') as f:
manifest_data = f.read()
# also creates a west.yml with a syntax error to verify west doesn't even
# try to load the file
add_commit(str(manifest), 'rename manifest',
files={'west.yml': '[', 'project.yml': manifest_data})
# syntax error
with pytest.raises(subprocess.CalledProcessError):
cmd(f'init -m "{manifest}" "{west_tmpdir}"')
shutil.move(west_tmpdir, repos_tmpdir / 'workspace-syntaxerror')
# success
cmd(f'init -m "{manifest}" --mf project.yml "{west_tmpdir}"')
west_tmpdir.chdir()
config.read_config()
cmd('update')
def test_extension_command_execution(west_init_tmpdir): def test_extension_command_execution(west_init_tmpdir):
with pytest.raises(subprocess.CalledProcessError): with pytest.raises(subprocess.CalledProcessError):
cmd('test-extension') cmd('test-extension')