Skip to content

Commit 552f964

Browse files
support 'west init --topdir'
Added new argument for west init to specify the west topdir when initializing a workspace from a local manifest.
1 parent a3af732 commit 552f964

File tree

2 files changed

+231
-39
lines changed

2 files changed

+231
-39
lines changed

src/west/app/project.py

Lines changed: 98 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,18 @@
2424
from west import util
2525
from west.commands import CommandError, Verbosity, WestCommand
2626
from west.configuration import Configuration
27-
from west.manifest import MANIFEST_REV_BRANCH as MANIFEST_REV
28-
from west.manifest import QUAL_MANIFEST_REV_BRANCH as QUAL_MANIFEST_REV
29-
from west.manifest import QUAL_REFS_WEST as QUAL_REFS
3027
from west.manifest import (
28+
_WEST_YML,
3129
ImportFlag,
3230
Manifest,
3331
ManifestImportFailed,
3432
ManifestProject,
3533
Submodule,
3634
_manifest_content_at,
3735
)
36+
from west.manifest import MANIFEST_REV_BRANCH as MANIFEST_REV
37+
from west.manifest import QUAL_MANIFEST_REV_BRANCH as QUAL_MANIFEST_REV
38+
from west.manifest import QUAL_REFS_WEST as QUAL_REFS
3839
from west.manifest import is_group as is_project_group
3940

4041
#
@@ -155,19 +156,54 @@ def __init__(self):
155156
'init',
156157
'create a west workspace',
157158
f'''\
158-
Creates a west workspace.
159-
160-
With -l, creates a workspace around an existing local repository;
161-
without -l, creates a workspace by cloning a manifest repository
162-
by URL.
163-
164-
With -m, clones the repository at that URL and uses it as the
165-
manifest repository. If --mr is not given, the remote's default
166-
branch will be used, if it exists.
167-
168-
With neither, -m {MANIFEST_URL_DEFAULT} is assumed.
169-
170-
Warning: 'west init' renames and/or deletes temporary files inside the
159+
Initialize a west workspace.
160+
161+
West supports two ways of creating a workspace:
162+
163+
1. Remote Manifest Repositories
164+
-------------------------------
165+
If `-m / --manifest-url` is given, west clones the manifest repository from the
166+
specified URL into a new workspace. Inside that workspace, the local config
167+
option `manifest.path` will be set to point at this cloned directory (relative
168+
path from the git clone to the workspace).
169+
170+
- If `-m / --manifest-url` is not provided, west will use Zephyr manifest
171+
repository by default:
172+
{MANIFEST_URL_DEFAULT}.
173+
- With `--mr`, you can specify the revision (branch, tag, or sha) of the
174+
manifest repository.
175+
- If `--mr / --manifest-revision` is omitted, the repository's default branch
176+
is used (if available).
177+
178+
Note: The west workspace (topdir) will be the current working directory, if
179+
neither --topdir nor [directory] are provided.
180+
181+
2. Local Manifest
182+
-----------------
183+
If `-l / --local` is given, west initializes a new workspace from an already
184+
existing local manifest. The directory containing the manifest can be provided
185+
in positional argument `[manifest_directory]` (defaults to current working dir)
186+
Inside that workspace, the local config option `manifest.path` will be set to
187+
point at the manifest directory (relative path from `manifest_directory` to the
188+
workspace)
189+
190+
Note: The west workspace (topdir) will be the `manifest_directory`'s parent, if
191+
no other directory is provided in `--topdir`.
192+
193+
Arguments
194+
---------
195+
--mf
196+
The relative path to the manifest file within the manifest repository
197+
(remote or local). Config option manifest.file will be set to this value.
198+
Defaults to `{_WEST_YML}` if not provided.
199+
200+
--topdir
201+
Specifies the directory where west should create the workspace.
202+
The `.west` folder will be created inside this directory.
203+
204+
Known Issues
205+
------------
206+
'west init' renames and/or deletes temporary files inside the
171207
workspace being created. This fails on some filesystems when some
172208
development tool or any other program is trying to read/index these
173209
temporary files at the same time. For instance, it is required to stop
@@ -192,9 +228,10 @@ def do_add_parser(self, parser_adder):
192228
parser = self._parser(
193229
parser_adder,
194230
usage='''
195-
196-
%(prog)s [-m URL] [--mr REVISION] [--mf FILE] [-o=GIT_CLONE_OPTION] [directory]
197-
%(prog)s -l [--mf FILE] directory
231+
remote repository:
232+
%(prog)s [-m URL] [--mr REVISION] [--mf FILE] [-o=GIT_CLONE_OPTION] [-t WORKSPACE_DIR | directory]
233+
local manifest:
234+
%(prog)s -l [-t WORKSPACE_DIR] [--mf FILE] [manifest_directory]
198235
''',
199236
)
200237

@@ -203,14 +240,16 @@ def do_add_parser(self, parser_adder):
203240
parser.add_argument(
204241
'-m',
205242
'--manifest-url',
206-
help='''manifest repository URL to clone;
243+
metavar='URL',
244+
help='''remote manifest repository URL to clone;
207245
cannot be combined with -l''',
208246
)
209247
parser.add_argument(
210248
'-o',
211249
'--clone-opt',
212250
action='append',
213251
default=[],
252+
metavar='GIT_CLONE_OPTION',
214253
help='''additional option to pass to 'git clone'
215254
(e.g. '-o=--depth=1'); may be given more than once;
216255
cannot be combined with -l''',
@@ -219,21 +258,33 @@ def do_add_parser(self, parser_adder):
219258
'--mr',
220259
'--manifest-rev',
221260
dest='manifest_rev',
261+
metavar='REVISION',
222262
help='''manifest repository branch or tag name
223263
to check out first; cannot be combined with -l''',
224264
)
225-
parser.add_argument(
226-
'--mf', '--manifest-file', dest='manifest_file', help='manifest file name to use'
227-
)
228265
parser.add_argument(
229266
'-l',
230267
'--local',
231268
action='store_true',
232-
help='''use "directory" as an existing local
233-
manifest repository instead of cloning one from
234-
MANIFEST_URL; .west is created next to "directory"
235-
in this case, and manifest.path points at
236-
"directory"''',
269+
help='''initialize from an already existing local
270+
manifest instead of cloning a remote manifest.''',
271+
)
272+
parser.add_argument(
273+
'--mf',
274+
'--manifest-file',
275+
dest='manifest_file',
276+
metavar='FILE',
277+
help=f'''manifest file to use. It is the relative
278+
path of the manifest file within the repository
279+
(remote or local). Defaults to {_WEST_YML}.''',
280+
)
281+
parser.add_argument(
282+
'-t',
283+
'--topdir',
284+
dest='topdir',
285+
metavar='WORKSPACE_DIR',
286+
help='''the directory of the west workspace, where
287+
.west will be created in.''',
237288
)
238289
parser.add_argument(
239290
'--rename-delay',
@@ -249,9 +300,10 @@ def do_add_parser(self, parser_adder):
249300
'directory',
250301
nargs='?',
251302
default=None,
252-
help='''with -l, the path to the local manifest repository;
253-
without it, the directory to create the workspace in (defaulting
254-
to the current working directory in this case)''',
303+
metavar='directory',
304+
help='''with --local: the path to the local manifest repository
305+
which contains a west.yml;
306+
otherwise: the directory to create the workspace in''',
255307
)
256308

257309
return parser
@@ -302,15 +354,19 @@ def local(self, args) -> Path:
302354
#
303355
# https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.parent
304356
manifest_dir = Path(args.directory or os.getcwd()).resolve()
305-
manifest_filename = args.manifest_file or 'west.yml'
357+
manifest_filename = args.manifest_file or _WEST_YML
306358
manifest_file = manifest_dir / manifest_filename
307-
topdir = manifest_dir.parent
308-
rel_manifest = manifest_dir.name
309-
west_dir = topdir / WEST_DIR
310-
311359
if not manifest_file.is_file():
312360
self.die(f'can\'t init: no {manifest_filename} found in {manifest_dir}')
313361

362+
topdir = Path(args.topdir or manifest_dir.parent).resolve()
363+
364+
if not manifest_file.is_relative_to(topdir):
365+
self.die(f'{manifest_file} must be relative to west topdir')
366+
367+
rel_manifest = manifest_dir.relative_to(topdir)
368+
west_dir = topdir / WEST_DIR
369+
314370
self.banner('Initializing from existing manifest repository', rel_manifest)
315371
self.small_banner(f'Creating {west_dir} and local configuration file')
316372
self.create(west_dir)
@@ -322,8 +378,11 @@ def local(self, args) -> Path:
322378
return topdir
323379

324380
def bootstrap(self, args) -> Path:
325-
topdir = Path(abspath(args.directory or os.getcwd()))
326-
self.banner('Initializing in', topdir)
381+
if args.topdir and args.directory:
382+
self.die('--topdir cannot be combined with positional argument [directory]')
383+
384+
topdir = Path(abspath(args.topdir or args.directory or os.getcwd()))
385+
self.banner(f'Initializing in {topdir}')
327386

328387
manifest_url = args.manifest_url or MANIFEST_URL_DEFAULT
329388
if args.manifest_rev:
@@ -378,7 +437,7 @@ def bootstrap(self, args) -> Path:
378437
raise
379438

380439
# Verify the manifest file exists.
381-
temp_manifest_filename = args.manifest_file or 'west.yml'
440+
temp_manifest_filename = args.manifest_file or _WEST_YML
382441
temp_manifest = tempdir / temp_manifest_filename
383442
if not temp_manifest.is_file():
384443
self.die(

tests/test_project.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1862,6 +1862,139 @@ def test_init_local_with_manifest_filename(repos_tmpdir):
18621862
cmd('update')
18631863

18641864

1865+
TEST_CASES_INIT_TOPDIR = [
1866+
# (local_dir, topdir, directory, manifest_file, expected_error)
1867+
######################################
1868+
# REMOTE MANIFEST
1869+
######################################
1870+
# init from remote repository (without any parameters)
1871+
(None, None, None, None, None),
1872+
# specify topdir in current directory
1873+
(None, Path('.'), None, None, None),
1874+
# specify topdir in a subfolder
1875+
(None, Path('subdir'), None, None, None),
1876+
# use deprecated [directory] to specify topdir
1877+
(None, None, Path('subdir'), None, None),
1878+
# specify topdir in a sibling
1879+
(None, Path('..') / 'sibling', None, None, None),
1880+
# error cases
1881+
# specify topdir and [directory]
1882+
(None, Path('subdir'), Path('not-used'), None, subprocess.CalledProcessError),
1883+
# specify a non-existent manifest file
1884+
(None, Path('.'), None, Path('non-existent.yml'), subprocess.CalledProcessError),
1885+
######################################
1886+
# LOCAL MANIFEST
1887+
######################################
1888+
# init workspace in current working directory (without --topdir)
1889+
(Path('workspace') / 'zephyr', None, Path('workspace') / 'zephyr', None, None),
1890+
# init workspace in current working directory
1891+
(Path('workspace') / 'zephyr', Path('.'), Path('workspace') / 'zephyr', None, None),
1892+
# init workspace in a subfolder of current working directory
1893+
(Path('workspace') / 'zephyr', Path('workspace'), Path('workspace') / 'zephyr', None, None),
1894+
# init workspace in current working directory by providing a manifest file
1895+
(Path('workspace') / 'zephyr', Path('.'), Path('workspace'), Path('zephyr') / 'west.yml', None),
1896+
# init workspace in itself by providing manifest file
1897+
(
1898+
Path('workspace') / 'zephyr',
1899+
Path('.'),
1900+
Path('.'),
1901+
Path('workspace') / 'zephyr' / 'west.yml',
1902+
None,
1903+
),
1904+
# init workspace in a subfolder by providing manifest file
1905+
(
1906+
Path('workspace') / 'subdir' / 'zephyr',
1907+
Path('workspace'),
1908+
Path('workspace') / 'subdir',
1909+
Path('zephyr') / 'west.yml',
1910+
None,
1911+
),
1912+
# init workspace in a subfolder by providing manifest file
1913+
(
1914+
Path('workspace') / 'subdir' / 'zephyr',
1915+
Path('workspace'),
1916+
Path('workspace'),
1917+
Path('subdir') / 'zephyr' / 'west.yml',
1918+
None,
1919+
),
1920+
# error cases
1921+
# init workspace without a directory
1922+
(Path('workspace') / 'zephyr', Path('.'), None, None, subprocess.CalledProcessError),
1923+
# init workspace in a sibling repository path
1924+
(
1925+
Path('workspace') / 'zephyr',
1926+
Path('sibling'),
1927+
Path('workspace') / 'zephyr',
1928+
None,
1929+
subprocess.CalledProcessError,
1930+
),
1931+
# init workspace from non-existent manifest
1932+
(
1933+
Path('workspace') / 'zephyr',
1934+
Path('.'),
1935+
Path('non-existent.yml'),
1936+
None,
1937+
subprocess.CalledProcessError,
1938+
),
1939+
# init workspace from a manifest not inside the workspace
1940+
(Path('..') / 'zephyr', Path('.'), Path('..') / 'zephyr', None, subprocess.CalledProcessError),
1941+
]
1942+
1943+
1944+
@pytest.mark.parametrize("test_case", TEST_CASES_INIT_TOPDIR)
1945+
def test_init(repos_tmpdir, test_case):
1946+
repos_tmpdir.chdir()
1947+
flags = []
1948+
1949+
local_dir, topdir, directory, manifest_file, expected_error = test_case
1950+
1951+
# prepare local manifest in local_dir
1952+
if local_dir:
1953+
# place the local manifest to given path
1954+
clone(str(repos_tmpdir / 'repos' / 'zephyr'), str(local_dir))
1955+
flags += ['-l']
1956+
else:
1957+
# clone from remote manifest
1958+
flags += ["-m", repos_tmpdir / 'repos' / 'zephyr']
1959+
1960+
# extend west init flags according to specified test case
1961+
if topdir:
1962+
flags += ['-t', topdir]
1963+
if manifest_file:
1964+
flags += ['--mf', manifest_file]
1965+
if directory:
1966+
flags += [directory]
1967+
1968+
# initialize west workspace
1969+
if not expected_error:
1970+
cmd(['init'] + flags)
1971+
else:
1972+
cmd_raises(['init'] + flags, expected_error)
1973+
return
1974+
1975+
# go to west workspace and check for correct config
1976+
if local_dir:
1977+
# topdir is either specified or default (directory.parent)
1978+
workspace = topdir or directory.parent
1979+
else:
1980+
# topdir is either specified, directory or default (cwd)
1981+
workspace = topdir or directory or Path.cwd()
1982+
1983+
os.chdir(workspace)
1984+
actual = cmd('config manifest.path')
1985+
if local_dir:
1986+
assert Path(actual.rstrip()) == directory.relative_to(workspace)
1987+
else:
1988+
assert Path(actual.rstrip()) == Path('zephyr')
1989+
1990+
manifest_file = manifest_file or Path('west.yml')
1991+
actual = cmd('config manifest.file')
1992+
assert Path(actual.rstrip()) == Path(manifest_file)
1993+
1994+
# update must run successful
1995+
cmd('update')
1996+
1997+
18651998
def test_init_local_with_empty_path(repos_tmpdir):
18661999
# Test "west init -l ." + "west update".
18672000
# Regression test for:

0 commit comments

Comments
 (0)