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
single file, called the *west manifest file*, or *manifest* for short. The
manifest file is named ``west.yml``. You use ``west init`` to set up this
directory, then ``west update`` to fetch and/or update the repositories named
in the manifest.
single file, called the *west manifest file*, or *manifest* for short.
By default the manifest file is named ``west.yml``.
You use ``west init`` to set up this directory, then ``west update`` to fetch
and/or update the repositories named in the manifest.
By default, west uses `upstream Zephyr's manifest file
<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``.
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
-------------------

View File

@ -181,7 +181,7 @@ class WestApp:
list(self.mle.args)))
elif isinst(FileNotFoundError):
# 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}")
elif isinst(_ManifestImportDepth):
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
from a git URL to a temporary subdirectory of .west,
.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
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',
help='''manifest revision to check out and use;
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',
help='''use an existing local manifest repository
instead of cloning one; cannot be combined with
@ -212,13 +214,15 @@ class Init(_ProjectCommand):
log.die('--mr cannot be used with -l')
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
rel_manifest = manifest_dir.name
west_dir = topdir / WEST_DIR
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',
rel_manifest)
@ -226,6 +230,7 @@ class Init(_ProjectCommand):
self.create(west_dir)
os.chdir(topdir)
update_config('manifest', 'path', os.fspath(rel_manifest))
update_config('manifest', 'file', manifest_filename, topdir=topdir)
return topdir
@ -257,9 +262,11 @@ class Init(_ProjectCommand):
raise
# 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():
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'--manifest-rev={manifest_rev}\n'
f' You may need to remove {west_dir} before retrying.')
@ -290,6 +297,8 @@ class Init(_ProjectCommand):
log.die(e)
log.small_banner('setting manifest.path to', manifest_path)
update_config('manifest', 'path', manifest_path, topdir=topdir)
update_config('manifest', 'file', temp_manifest_filename,
topdir=topdir)
return topdir
@ -640,15 +649,15 @@ class Update(_ProjectCommand):
def __init__(self):
super().__init__(
'update',
'update projects described in west.yml',
'update projects described in west manifest',
textwrap.dedent('''\
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
revision is available locally
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
(but see "checked out branches")

View File

@ -17,7 +17,7 @@ import shlex
import subprocess
import sys
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
import pykwalify.core
@ -138,7 +138,7 @@ def _west_commands_merge(wc1: List[str], wc2: List[str]) -> List[str]:
return wc1 or wc2
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
# in *cp*, a ConfigParser. If not given, create a new one and
# 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)
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:
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
``manifest.path`` key
- ``FileNotFoundError`` if no ``west.yml`` exists in
``manifest.path``
- ``FileNotFoundError`` if no manifest file exists as determined by
``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.
# This seems to be the best way.
if not os.path.isfile(ret):
@ -897,7 +901,7 @@ class Manifest:
- If neither *source_file* nor *topdir* is given, the file
system is searched for *topdir*. That workspace's
``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
starting there. The directory containing *source_file*
@ -932,10 +936,10 @@ class Manifest:
# neither source_file nor topdir: search the filesystem
# for the workspace and use its manifest.path.
topdir = util.west_topdir()
mpath = _mpath(topdir=topdir)
(mpath, mname) = _mpath(topdir=topdir)
kwargs.update({
'topdir': topdir,
'source_file': os.path.join(topdir, mpath, _WEST_YML),
'source_file': os.path.join(topdir, mpath, mname),
'manifest_path': mpath
})
else:
@ -960,8 +964,8 @@ class Manifest:
# Read manifest.path from topdir/.west/config, and use it
# to locate source_file.
mpath = _mpath(topdir=topdir)
source_file = os.path.join(topdir, mpath, _WEST_YML)
(mpath, mname) = _mpath(topdir=topdir)
source_file = os.path.join(topdir, mpath, mname)
kwargs.update({
'source_file': source_file,
'manifest_path': mpath,
@ -1747,7 +1751,7 @@ class Manifest:
except FileNotFoundError:
# We may need to fetch a new manifest-rev, e.g. if
# 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)
except subprocess.CalledProcessError:
# 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}"')
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):
with pytest.raises(subprocess.CalledProcessError):
cmd('test-extension')