Skip to content

Commit 28981bd

Browse files
support 'west init --topdir'
Added new argument for west init to specify the west topdir when a workspace is inizialized.
1 parent 11302e1 commit 28981bd

File tree

2 files changed

+132
-50
lines changed

2 files changed

+132
-50
lines changed

src/west/app/project.py

Lines changed: 132 additions & 41 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
from west.util import expand_path
4041

@@ -156,19 +157,58 @@ def __init__(self):
156157
'init',
157158
'create a west workspace',
158159
f'''\
159-
Creates a west workspace.
160-
161-
With -l, creates a workspace around an existing local repository;
162-
without -l, creates a workspace by cloning a manifest repository
163-
by URL.
164-
165-
With -m, clones the repository at that URL and uses it as the
166-
manifest repository. If --mr is not given, the remote's default
167-
branch will be used, if it exists.
160+
Initialize a west workspace (topdir) from a west manifest (`west.yml`) by
161+
creating a `.west` directory in the topdir and a local configuration file
162+
`.west/config`.
163+
The West manifest can come from either a git repository (that will be cloned
164+
during workspace initialization) or from an already existing local directory.
168165
169-
With neither, -m {MANIFEST_URL_DEFAULT} is assumed.
170-
171-
Warning: 'west init' renames and/or deletes temporary files inside the
166+
Arguments
167+
---------
168+
--mf / --manifest-file
169+
The relative path to the manifest file within the manifest repository
170+
or directory. Config option `manifest.file` will be set to this value.
171+
Defaults to '{_WEST_YML}' if not provided.
172+
173+
-t / --topdir
174+
Specifies the directory where west should create the workspace.
175+
The `.west` folder will be created inside this directory.
176+
If it is an already existing directory, it must not contain a .west folder.
177+
178+
179+
1. Using a Manifest Repository (default)
180+
----------------------------------------
181+
West clones the given repository (provided via `-m / --manifest-url`).
182+
Note, that the repository must contain a west manifest.
183+
184+
If no `-m / --manifest-url` is provided, west uses Zephyr URL by default:
185+
{MANIFEST_URL_DEFAULT}.
186+
187+
The topdir (where `.west` is created) is determined as follows:
188+
- argument `-t / --topdir` if provided
189+
- otherwise: the positional argument `directory` if provided
190+
- otherwise: the current working directory
191+
192+
If both `-t / --topdir` and `directory` are provided, `-t / --topdir`
193+
specifies the workspace directory, while positional argument `directory`
194+
specifies the subfolder within the workspace where the manifest repository is
195+
cloned during initialization (defaults to <directory>/zephyr/.git).
196+
With `--mr`, the revision (branch, tag, or sha) of the repository can be
197+
specified that will be used. It defaults to the repository's default branch.
198+
199+
2. Using a Local Manifest
200+
-------------------------
201+
If `-l / --local` is given, west initializes a workspace from an already
202+
existing `west.yml`. Its location can be specified in the `manifest_directory`
203+
positional argument, which defaults to current working directory.
204+
205+
The topdir (where `.west` is created) is determined as follows:
206+
- argument `-t / --topdir` if provided
207+
- otherwise: `manifest_directory`'s parent
208+
209+
Known Issues
210+
------------
211+
'west init' renames and/or deletes temporary files inside the
172212
workspace being created. This fails on some filesystems when some
173213
development tool or any other program is trying to read/index these
174214
temporary files at the same time. For instance, it is required to stop
@@ -193,9 +233,11 @@ def do_add_parser(self, parser_adder):
193233
parser = self._parser(
194234
parser_adder,
195235
usage='''
236+
init with a repository:
237+
%(prog)s [-m URL] [--mr REVISION] [--mf FILE] [-o=GIT_CLONE_OPTION] [-t WORKSPACE_DIR] [directory]
196238
197-
%(prog)s [-m URL] [--mr REVISION] [--mf FILE] [-o=GIT_CLONE_OPTION] [directory]
198-
%(prog)s -l [--mf FILE] directory
239+
init with an existing local manifest:
240+
%(prog)s -l [-t WORKSPACE_DIR] [--mf FILE] [manifest_directory]
199241
''',
200242
)
201243

@@ -204,6 +246,7 @@ def do_add_parser(self, parser_adder):
204246
parser.add_argument(
205247
'-m',
206248
'--manifest-url',
249+
metavar='URL',
207250
help='''manifest repository URL to clone;
208251
cannot be combined with -l''',
209252
)
@@ -212,6 +255,7 @@ def do_add_parser(self, parser_adder):
212255
'--clone-opt',
213256
action='append',
214257
default=[],
258+
metavar='GIT_CLONE_OPTION',
215259
help='''additional option to pass to 'git clone'
216260
(e.g. '-o=--depth=1'); may be given more than once;
217261
cannot be combined with -l''',
@@ -220,21 +264,36 @@ def do_add_parser(self, parser_adder):
220264
'--mr',
221265
'--manifest-rev',
222266
dest='manifest_rev',
267+
metavar='REVISION',
223268
help='''manifest repository branch or tag name
224269
to check out first; cannot be combined with -l''',
225270
)
226-
parser.add_argument(
227-
'--mf', '--manifest-file', dest='manifest_file', help='manifest file name to use'
228-
)
229271
parser.add_argument(
230272
'-l',
231273
'--local',
232274
action='store_true',
233-
help='''use "directory" as an existing local
234-
manifest repository instead of cloning one from
235-
MANIFEST_URL; .west is created next to "directory"
236-
in this case, and manifest.path points at
237-
"directory"''',
275+
help='''initialize workspace from an already existing local
276+
manifest instead of cloning a manifest;
277+
cannot be combined with -m''',
278+
)
279+
parser.add_argument(
280+
'--mf',
281+
'--manifest-file',
282+
dest='manifest_file',
283+
metavar='FILE',
284+
help=f'''manifest file to use. It is the relative
285+
path to the manifest file within the manifest_directory.
286+
Defaults to {_WEST_YML}.''',
287+
)
288+
parser.add_argument(
289+
'-t',
290+
'--topdir',
291+
dest='topdir',
292+
metavar='WORKSPACE_DIR',
293+
help='''the workspace directory where .west directory
294+
will be created (WORKSPACE_DIR/.west);
295+
the workspace directory may already exist
296+
and WORKSPACE_DIR/.west must not exist''',
238297
)
239298
parser.add_argument(
240299
'--rename-delay',
@@ -250,9 +309,15 @@ def do_add_parser(self, parser_adder):
250309
'directory',
251310
nargs='?',
252311
default=None,
253-
help='''with -l, the path to the local manifest repository;
254-
without it, the directory to create the workspace in (defaulting
255-
to the current working directory in this case)''',
312+
metavar='directory',
313+
help='''With --local: the path to a local directory which contains
314+
a west manifest (west.yml);
315+
Otherwise, if no --topdir is provided:
316+
the location of the west workspace where .west directory
317+
will be created (<directory>/.west)
318+
Otherwise:
319+
the location where west will clone the manifest repository
320+
''',
256321
)
257322

258323
return parser
@@ -302,17 +367,27 @@ def local(self, args) -> Path:
302367
# Path('..').
303368
#
304369
# https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.parent
305-
manifest_dir = Path(args.directory or os.getcwd()).resolve()
306-
manifest_filename = args.manifest_file or 'west.yml'
370+
manifest_dir = Path(args.directory or '.').resolve()
371+
manifest_filename = args.manifest_file or _WEST_YML
307372
manifest_file = manifest_dir / manifest_filename
308-
topdir = manifest_dir.parent
309-
rel_manifest = manifest_dir.name
310-
west_dir = topdir / WEST_DIR
311-
312373
if not manifest_file.is_file():
313374
self.die(f'can\'t init: no {manifest_filename} found in {manifest_dir}')
314375

315-
self.banner('Initializing from existing manifest repository', rel_manifest)
376+
topdir = Path(args.topdir or manifest_dir.parent).resolve()
377+
378+
if not manifest_file.is_relative_to(topdir):
379+
self.die(f'{manifest_file} must be relative to west topdir {topdir}')
380+
381+
try:
382+
already = util.west_topdir(manifest_dir, fall_back=False)
383+
self.die_already(already)
384+
except util.WestNotFound:
385+
pass
386+
387+
rel_manifest = manifest_dir.relative_to(topdir)
388+
west_dir = topdir / WEST_DIR
389+
390+
self.banner('Initializing around existing manifest', rel_manifest)
316391
self.small_banner(f'Creating {west_dir} and local configuration file')
317392
self.create(west_dir)
318393
os.chdir(topdir)
@@ -323,8 +398,22 @@ def local(self, args) -> Path:
323398
return topdir
324399

325400
def bootstrap(self, args) -> Path:
326-
topdir = Path(abspath(args.directory or os.getcwd()))
327-
self.banner('Initializing in', topdir)
401+
manifest_subdir = Path()
402+
if args.topdir:
403+
topdir = Path(args.topdir)
404+
if args.directory:
405+
if not Path(args.directory).is_relative_to(topdir):
406+
self.die(
407+
f"directory '{args.directory}' must be relative "
408+
f"to west topdir '{args.topdir}'"
409+
)
410+
manifest_subdir = Path(os.path.relpath(args.directory, topdir))
411+
elif args.directory:
412+
topdir = Path(args.directory)
413+
else:
414+
topdir = Path()
415+
topdir = topdir.absolute()
416+
self.banner(f'Initializing in {topdir}')
328417

329418
manifest_url = args.manifest_url or MANIFEST_URL_DEFAULT
330419
if args.manifest_rev:
@@ -379,7 +468,7 @@ def bootstrap(self, args) -> Path:
379468
raise
380469

381470
# Verify the manifest file exists.
382-
temp_manifest_filename = args.manifest_file or 'west.yml'
471+
temp_manifest_filename = args.manifest_file or _WEST_YML
383472
temp_manifest = tempdir / temp_manifest_filename
384473
if not temp_manifest.is_file():
385474
self.die(
@@ -396,15 +485,17 @@ def bootstrap(self, args) -> Path:
396485
manifest = Manifest.from_data(
397486
temp_manifest.read_text(encoding=Manifest.encoding), import_flags=ImportFlag.IGNORE
398487
)
488+
399489
if manifest.yaml_path:
400-
manifest_path = manifest.yaml_path
490+
yaml_path = manifest.yaml_path
401491
else:
402492
# We use PurePath() here in case manifest_url is a
403493
# windows-style path. That does the right thing in that
404494
# case, without affecting POSIX platforms, where PurePath
405495
# is PurePosixPath.
406-
manifest_path = PurePath(urlparse(manifest_url).path).name
496+
yaml_path = PurePath(urlparse(manifest_url).path).name
407497

498+
manifest_path: Path = Path(manifest_subdir) / yaml_path
408499
manifest_abspath = topdir / manifest_path
409500

410501
# Some filesystems like NTFS can't rename files in use.
@@ -431,7 +522,7 @@ def bootstrap(self, args) -> Path:
431522
self.die(e)
432523
self.small_banner('setting manifest.path to', manifest_path)
433524
self.config = Configuration(topdir=topdir)
434-
self.config.set('manifest.path', manifest_path)
525+
self.config.set('manifest.path', str(manifest_path))
435526
self.config.set('manifest.file', temp_manifest_filename)
436527

437528
return topdir

tests/test_project.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -958,8 +958,6 @@ def test_update_some_with_imports(repos_tmpdir):
958958
},
959959
)
960960

961-
cmd(['init', '-l', manifest_repo])
962-
963961
# Updating unknown projects should fail as always.
964962

965963
with pytest.raises(SystemExit):
@@ -1037,7 +1035,6 @@ def test_update_submodules_list(repos_tmpdir):
10371035
'''
10381036
},
10391037
)
1040-
cmd(['init', '-l', manifest_repo])
10411038

10421039
# Make tagged_repo to be zephyr project submodule.
10431040
subprocess.check_call(SUBMODULE_ADD + [str(tagged_repo), 'tagged_repo'], cwd=zephyr)
@@ -1161,7 +1158,6 @@ def test_update_all_submodules(repos_tmpdir):
11611158
'''
11621159
},
11631160
)
1164-
cmd(['init', '-l', manifest_repo])
11651161

11661162
# Make tagged_repo to be zephyr project submodule.
11671163
subprocess.check_call(SUBMODULE_ADD + [str(tagged_repo), 'tagged_repo'], cwd=zephyr)
@@ -1273,7 +1269,6 @@ def test_update_no_submodules(repos_tmpdir):
12731269
'''
12741270
},
12751271
)
1276-
cmd(['init', '-l', manifest_repo])
12771272

12781273
# Make tagged_repo to be zephyr project submodule.
12791274
subprocess.check_call(SUBMODULE_ADD + [str(tagged_repo), 'tagged_repo'], cwd=zephyr)
@@ -1379,7 +1374,6 @@ def test_update_submodules_strategy(repos_tmpdir):
13791374
'''
13801375
},
13811376
)
1382-
cmd(['init', '-l', manifest_repo])
13831377

13841378
# Make tagged_repo to be zephyr project submodule.
13851379
subprocess.check_call(SUBMODULE_ADD + [str(tagged_repo), 'tagged_repo'], cwd=zephyr)
@@ -2623,7 +2617,6 @@ def test_import_project_release_fork(repos_tmpdir):
26232617
},
26242618
)
26252619

2626-
cmd(['init', '-l', manifest_repo])
26272620
with pytest.raises(ManifestImportFailed):
26282621
Manifest.from_topdir(topdir=ws)
26292622

@@ -2709,7 +2702,6 @@ def test_import_project_release_dir(tmpdir):
27092702
},
27102703
)
27112704

2712-
cmd(['init', '-l', manifest_repo])
27132705
with pytest.raises(ManifestImportFailed):
27142706
Manifest.from_topdir(topdir=ws)
27152707

@@ -2752,7 +2744,6 @@ def test_import_project_rolling(repos_tmpdir):
27522744
},
27532745
)
27542746

2755-
cmd(['init', '-l', manifest_repo])
27562747
with pytest.raises(ManifestImportFailed):
27572748
Manifest.from_topdir(topdir=ws)
27582749

0 commit comments

Comments
 (0)