Skip to content

Commit 8a2e15e

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 594a789 commit 8a2e15e

File tree

2 files changed

+252
-35
lines changed

2 files changed

+252
-35
lines changed

src/west/app/project.py

Lines changed: 81 additions & 35 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
#
@@ -151,19 +152,49 @@ def __init__(self):
151152
'init',
152153
'create a west workspace',
153154
f'''\
154-
Creates a west workspace.
155+
Initialize a west workspace.
155156
156-
With -l, creates a workspace around an existing local repository;
157-
without -l, creates a workspace by cloning a manifest repository
158-
by URL.
157+
West supports two ways of creating a workspace:
159158
160-
With -m, clones the repository at that URL and uses it as the
161-
manifest repository. If --mr is not given, the remote's default
162-
branch will be used, if it exists.
159+
1. Remote Manifest Repositories
160+
----------------------------
161+
If `-m` is given, west clones the manifest repository from the specified URL
162+
into the workspace. Config option manifest.path will point at this cloned
163+
directory.
163164
164-
With neither, -m {MANIFEST_URL_DEFAULT} is assumed.
165+
- If `-m` is not provided, west defaults to the Zephyr manifest repository:
166+
{MANIFEST_URL_DEFAULT}.
167+
- With `--mr`, you can specify the revision (branch, tag, or sha) of the
168+
manifest repository.
169+
- If `--mr` is omitted, the repository's default branch (if available) is used.
165170
166-
Warning: 'west init' renames and/or deletes temporary files inside the
171+
Note: The west workspace will be created in the current working directory, if
172+
neither --topdir nor [directory] are provided.
173+
174+
2. Local Manifest
175+
--------------
176+
If `--local` is given, west initializes a workspace from an already existing
177+
local manifest. The directory containing the manifest can be provided in
178+
positional argument [directory] (default: current working directory).
179+
Config option manifest.path will point at this directory.
180+
181+
Note: The west workspace will be created in the provided directory's parent, if
182+
no other directory is provided in --topdir.
183+
184+
Arguments
185+
---------
186+
--mf
187+
The relative path to the manifest file within the manifest repository
188+
(remote or local). Config option manifest.file will be set to this value.
189+
Defaults to `{_WEST_YML}` if not provided.
190+
191+
--topdir
192+
Specifies the directory where west should create the workspace.
193+
The `.west` folder will be created inside this directory.
194+
195+
Known Issues
196+
------------
197+
'west init' renames and/or deletes temporary files inside the
167198
workspace being created. This fails on some filesystems when some
168199
development tool or any other program is trying to read/index these
169200
temporary files at the same time. For instance, it is required to stop
@@ -187,31 +218,39 @@ def do_add_parser(self, parser_adder):
187218
parser = self._parser(
188219
parser_adder,
189220
usage='''
190-
191-
%(prog)s [-m URL] [--mr REVISION] [--mf FILE] [-o=GIT_CLONE_OPTION] [directory]
192-
%(prog)s -l [--mf FILE] directory
221+
remote repository:
222+
%(prog)s [-m URL] [--mr REVISION] [--mf FILE] [-o=GIT_CLONE_OPTION] [-t WORKSPACE_DIR] [directory]
223+
local manifest:
224+
%(prog)s -l [-t WORKSPACE_DIR] [--mf FILE] directory
193225
''')
194226

195227
# Remember to update the usage if you modify any arguments.
196228

197229
parser.add_argument('-m', '--manifest-url',
198-
help='''manifest repository URL to clone;
230+
metavar='URL',
231+
help='''remote manifest repository URL to clone;
199232
cannot be combined with -l''')
200233
parser.add_argument('-o', '--clone-opt', action='append', default=[],
234+
metavar='GIT_CLONE_OPTION',
201235
help='''additional option to pass to 'git clone'
202236
(e.g. '-o=--depth=1'); may be given more than once;
203237
cannot be combined with -l''')
204238
parser.add_argument('--mr', '--manifest-rev', dest='manifest_rev',
239+
metavar='REVISION',
205240
help='''manifest repository branch or tag name
206241
to check out first; cannot be combined with -l''')
207-
parser.add_argument('--mf', '--manifest-file', dest='manifest_file',
208-
help='manifest file name to use')
209242
parser.add_argument('-l', '--local', action='store_true',
210-
help='''use "directory" as an existing local
211-
manifest repository instead of cloning one from
212-
MANIFEST_URL; .west is created next to "directory"
213-
in this case, and manifest.path points at
214-
"directory"''')
243+
help='''initialize from an already existing local
244+
manifest instead of cloning a remote manifest.''')
245+
parser.add_argument('--mf', '--manifest-file', dest='manifest_file',
246+
metavar='FILE',
247+
help=f'''manifest file to use. It is the relative
248+
path of the manifest file within the repository
249+
(remote or local). Defaults to {_WEST_YML}.''')
250+
parser.add_argument('-t', '--topdir', dest='topdir',
251+
metavar='WORKSPACE_DIR',
252+
help='''the directory of the west workspace, where
253+
.west will be created in.''')
215254
parser.add_argument('--rename-delay', type=int,
216255
help='''Number of seconds to wait before renaming
217256
some temporary directories. Some filesystems like NTFS
@@ -220,10 +259,10 @@ def do_add_parser(self, parser_adder):
220259
background scanner to complete. ''')
221260

222261
parser.add_argument(
223-
'directory', nargs='?', default=None,
224-
help='''with -l, the path to the local manifest repository;
225-
without it, the directory to create the workspace in (defaulting
226-
to the current working directory in this case)''')
262+
'directory', nargs='?', default=None, metavar='directory',
263+
help='''with --local: the path to the local manifest repository
264+
which contains a west.yml;
265+
otherwise: the directory to create the workspace in''')
227266

228267
return parser
229268

@@ -271,16 +310,20 @@ def local(self, args) -> Path:
271310
#
272311
# https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.parent
273312
manifest_dir = Path(args.directory or os.getcwd()).resolve()
274-
manifest_filename = args.manifest_file or 'west.yml'
313+
manifest_filename = args.manifest_file or _WEST_YML
275314
manifest_file = manifest_dir / manifest_filename
276-
topdir = manifest_dir.parent
277-
rel_manifest = manifest_dir.name
278-
west_dir = topdir / WEST_DIR
279-
280315
if not manifest_file.is_file():
281316
self.die(f'can\'t init: no {manifest_filename} found in '
282317
f'{manifest_dir}')
283318

319+
topdir = Path(args.topdir or manifest_dir.parent).resolve()
320+
321+
if not manifest_file.is_relative_to(topdir):
322+
self.die(f'{manifest_file} must be relative to west topdir')
323+
324+
rel_manifest = manifest_dir.relative_to(topdir)
325+
west_dir = topdir / WEST_DIR
326+
284327
self.banner('Initializing from existing manifest repository',
285328
rel_manifest)
286329
self.small_banner(f'Creating {west_dir} and local configuration file')
@@ -293,8 +336,11 @@ def local(self, args) -> Path:
293336
return topdir
294337

295338
def bootstrap(self, args) -> Path:
296-
topdir = Path(abspath(args.directory or os.getcwd()))
297-
self.banner('Initializing in', topdir)
339+
if args.topdir and args.directory:
340+
self.die('--topdir cannot be combined with positional argument [directory]')
341+
342+
topdir = Path(abspath(args.topdir or args.directory or os.getcwd()))
343+
self.banner(f'Initializing in {topdir}')
298344

299345
manifest_url = args.manifest_url or MANIFEST_URL_DEFAULT
300346
if args.manifest_rev:
@@ -347,7 +393,7 @@ def bootstrap(self, args) -> Path:
347393
raise
348394

349395
# Verify the manifest file exists.
350-
temp_manifest_filename = args.manifest_file or 'west.yml'
396+
temp_manifest_filename = args.manifest_file or _WEST_YML
351397
temp_manifest = tempdir / temp_manifest_filename
352398
if not temp_manifest.is_file():
353399
self.die(f'can\'t init: no {temp_manifest_filename} found in '

tests/test_project.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1745,6 +1745,177 @@ def test_init_local_with_manifest_filename(repos_tmpdir):
17451745
cmd('update')
17461746

17471747

1748+
TEST_CASES_INIT_TOPDIR = [
1749+
# (local_dir, topdir, directory, manifest_file, expected_error)
1750+
1751+
######################################
1752+
# REMOTE MANIFEST
1753+
######################################
1754+
# init from remote repository (without any parameters)
1755+
(None, None, None, None, None),
1756+
# specify topdir in current directory
1757+
(None, Path('.'), None, None, None),
1758+
# specify topdir in a subfolder
1759+
(None, Path('subdir'), None, None, None),
1760+
# use deprecated [directory] to specify topdir
1761+
(None, None, Path('subdir'), None, None),
1762+
# specify topdir in a sibling
1763+
(None, Path('..') / 'sibling', None, None, None),
1764+
1765+
# error cases
1766+
# specify topdir and [directory]
1767+
(None, Path('subdir'), Path('not-used'), None, subprocess.CalledProcessError),
1768+
# specify a non-existent manifest file
1769+
(None, Path('.'), None, Path('non-existent.yml'), subprocess.CalledProcessError),
1770+
1771+
######################################
1772+
# LOCAL MANIFEST
1773+
######################################
1774+
# init workspace in current working directory (without --topdir)
1775+
(
1776+
Path('workspace') / 'zephyr',
1777+
None,
1778+
Path('workspace') / 'zephyr',
1779+
None,
1780+
None
1781+
),
1782+
# init workspace in current working directory
1783+
(
1784+
Path('workspace') / 'zephyr',
1785+
Path('.'),
1786+
Path('workspace') / 'zephyr',
1787+
None,
1788+
None
1789+
),
1790+
# init workspace in a subfolder of current working directory
1791+
(
1792+
Path('workspace') / 'zephyr',
1793+
Path('workspace'),
1794+
Path('workspace') / 'zephyr',
1795+
None,
1796+
None
1797+
),
1798+
# init workspace in current working directory by providing a manifest file
1799+
(
1800+
Path('workspace') / 'zephyr',
1801+
Path('.'),
1802+
Path('workspace'),
1803+
Path('zephyr') / 'west.yml',
1804+
None
1805+
),
1806+
# init workspace in itself by providing manifest file
1807+
(
1808+
Path('workspace') / 'zephyr',
1809+
Path('.'),
1810+
Path('.'),
1811+
Path('workspace') / 'zephyr' / 'west.yml',
1812+
None
1813+
),
1814+
# init workspace in a subfolder by providing manifest file
1815+
(
1816+
Path('workspace') / 'subdir' / 'zephyr',
1817+
Path('workspace'),
1818+
Path('workspace') / 'subdir',
1819+
Path('zephyr') / 'west.yml',
1820+
None
1821+
),
1822+
# init workspace in a subfolder by providing manifest file
1823+
(
1824+
Path('workspace') / 'subdir' / 'zephyr',
1825+
Path('workspace'),
1826+
Path('workspace'),
1827+
Path('subdir') / 'zephyr' / 'west.yml',
1828+
None
1829+
),
1830+
1831+
# error cases
1832+
# init workspace without a directory
1833+
(
1834+
Path('workspace') / 'zephyr',
1835+
Path('.'),
1836+
None,
1837+
None,
1838+
subprocess.CalledProcessError
1839+
),
1840+
# init workspace in a sibling repository path
1841+
(
1842+
Path('workspace') / 'zephyr',
1843+
Path('sibling'),
1844+
Path('workspace') / 'zephyr',
1845+
None,
1846+
subprocess.CalledProcessError
1847+
),
1848+
# init workspace from non-existent manifest
1849+
(
1850+
Path('workspace') / 'zephyr',
1851+
Path('.'),
1852+
Path('non-existent.yml'),
1853+
None,
1854+
subprocess.CalledProcessError
1855+
),
1856+
# init workspace from a manifest not inside the workspace
1857+
(
1858+
Path('..') / 'zephyr',
1859+
Path('.'),
1860+
Path('..') / 'zephyr',
1861+
None,
1862+
subprocess.CalledProcessError
1863+
),
1864+
]
1865+
@pytest.mark.parametrize("test_case", TEST_CASES_INIT_TOPDIR)
1866+
def test_init(repos_tmpdir, test_case):
1867+
repos_tmpdir.chdir()
1868+
flags = []
1869+
1870+
local_dir, topdir, directory, manifest_file, expected_error = test_case
1871+
1872+
# prepare local manifest in local_dir
1873+
if local_dir:
1874+
# place the local manifest to given path
1875+
clone(str(repos_tmpdir / 'repos' / 'zephyr'), str(local_dir))
1876+
flags += ['-l']
1877+
else:
1878+
# clone from remote manifest
1879+
flags += ["-m", repos_tmpdir / 'repos' / 'zephyr']
1880+
1881+
# extend west init flags according to specified test case
1882+
if topdir:
1883+
flags += ['-t', topdir]
1884+
if manifest_file:
1885+
flags += ['--mf', manifest_file]
1886+
if directory:
1887+
flags += [directory]
1888+
1889+
# initialize west workspace
1890+
if not expected_error:
1891+
cmd(['init'] + flags)
1892+
else:
1893+
cmd_raises(['init'] + flags, expected_error)
1894+
return
1895+
1896+
# go to west workspace and check for correct config
1897+
if local_dir:
1898+
# topdir is either specified or default (directory.parent)
1899+
workspace = topdir or directory.parent
1900+
else:
1901+
# topdir is either specified, directory or default (cwd)
1902+
workspace = topdir or directory or Path.cwd()
1903+
1904+
os.chdir(workspace)
1905+
actual = cmd('config manifest.path')
1906+
if local_dir:
1907+
assert Path(actual.rstrip()) == directory.relative_to(workspace)
1908+
else:
1909+
assert Path(actual.rstrip()) == Path('zephyr')
1910+
1911+
manifest_file = manifest_file or Path('west.yml')
1912+
actual = cmd('config manifest.file')
1913+
assert Path(actual.rstrip()) == Path(manifest_file)
1914+
1915+
# update must run successful
1916+
cmd('update')
1917+
1918+
17481919
def test_init_local_with_empty_path(repos_tmpdir):
17491920
# Test "west init -l ." + "west update".
17501921
# Regression test for:

0 commit comments

Comments
 (0)