Skip to content

Commit b79c87b

Browse files
committed
ENH: remove the need for a working directory
Currently a Project has an associated a temporary working directory containing the build directory (unless the user specifies another one via configuration options) and the install directory for the meson project. The code can be simplified assigning to the project a build directory and always using a temporary install directory. Note that currently all files are copied into the Python wheel from the source or build directory, thus running meson install into a destination directory is not that useful. This will be fixes in later commits.
1 parent 0e20851 commit b79c87b

File tree

5 files changed

+50
-64
lines changed

5 files changed

+50
-64
lines changed

mesonpy/__init__.py

Lines changed: 35 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -489,30 +489,33 @@ def _wheel_write_metadata(self, whl: mesonpy._wheelfile.WheelFile) -> None:
489489
def build(self, directory: Path) -> pathlib.Path:
490490
# ensure project is built
491491
self._project.build()
492-
# install the project
493-
self._project.install()
494492

495-
wheel_file = pathlib.Path(directory, f'{self.name}.whl')
496-
with mesonpy._wheelfile.WheelFile(wheel_file, 'w') as whl:
497-
self._wheel_write_metadata(whl)
493+
# install project in temporary destination directory
494+
with tempfile.TemporaryDirectory() as destdir:
495+
self._project.install(destdir)
498496

499-
with mesonpy._util.cli_counter(sum(len(x) for x in self._wheel_files.values())) as counter:
497+
wheel_file = pathlib.Path(directory, f'{self.name}.whl')
500498

501-
root = 'purelib' if self.is_pure else 'platlib'
499+
with mesonpy._wheelfile.WheelFile(wheel_file, 'w') as whl:
500+
self._wheel_write_metadata(whl)
502501

503-
for path, entries in self._wheel_files.items():
504-
for dst, src in entries:
505-
counter.update(src)
502+
with mesonpy._util.cli_counter(sum(len(x) for x in self._wheel_files.values())) as counter:
506503

507-
if path == root:
508-
pass
509-
elif path == 'mesonpy-libs':
510-
# custom installation path for bundled libraries
511-
dst = pathlib.Path(f'.{self._project.name}.mesonpy.libs', dst)
512-
else:
513-
dst = pathlib.Path(self.data_dir, path, dst)
504+
root = 'purelib' if self.is_pure else 'platlib'
505+
506+
for path, entries in self._wheel_files.items():
507+
for dst, src in entries:
508+
counter.update(src)
514509

515-
self._install_path(whl, src, dst)
510+
if path == root:
511+
pass
512+
elif path == 'mesonpy-libs':
513+
# custom installation path for bundled libraries
514+
dst = pathlib.Path(f'.{self._project.name}.mesonpy.libs', dst)
515+
else:
516+
dst = pathlib.Path(self.data_dir, path, dst)
517+
518+
self._install_path(whl, src, dst)
516519

517520
return wheel_file
518521

@@ -628,16 +631,13 @@ class Project():
628631
def __init__(
629632
self,
630633
source_dir: Path,
631-
working_dir: Path,
632-
build_dir: Optional[Path] = None,
634+
build_dir: Path,
633635
meson_args: Optional[MesonArgs] = None,
634636
editable_verbose: bool = False,
635637
) -> None:
636638
self._source_dir = pathlib.Path(source_dir).absolute()
637-
self._working_dir = pathlib.Path(working_dir).absolute()
638-
self._build_dir = pathlib.Path(build_dir).absolute() if build_dir else (self._working_dir / 'build')
639+
self._build_dir = pathlib.Path(build_dir).absolute()
639640
self._editable_verbose = editable_verbose
640-
self._install_dir = self._working_dir / 'install'
641641
self._meson_native_file = self._build_dir / 'meson-python-native-file.ini'
642642
self._meson_cross_file = self._build_dir / 'meson-python-cross-file.ini'
643643
self._meson_args: MesonArgs = collections.defaultdict(list)
@@ -651,7 +651,6 @@ def __init__(
651651

652652
# make sure the build dir exists
653653
self._build_dir.mkdir(exist_ok=True, parents=True)
654-
self._install_dir.mkdir(exist_ok=True, parents=True)
655654

656655
# setuptools-like ARCHFLAGS environment variable support
657656
if sysconfig.get_platform().startswith('macosx-'):
@@ -805,24 +804,11 @@ def build(self) -> None:
805804
"""Build the Meson project."""
806805
self._run(self._build_command)
807806

808-
def install(self) -> None:
807+
def install(self, destdir: Path) -> None:
809808
"""Install the Meson project."""
810-
destdir = os.fspath(self._install_dir)
809+
destdir = os.fspath(destdir)
811810
self._run(['meson', 'install', '--quiet', '--no-rebuild', '--destdir', destdir, *self._meson_args['install']])
812811

813-
@classmethod
814-
@contextlib.contextmanager
815-
def with_temp_working_dir(
816-
cls,
817-
source_dir: Path = os.path.curdir,
818-
build_dir: Optional[Path] = None,
819-
meson_args: Optional[MesonArgs] = None,
820-
editable_verbose: bool = False,
821-
) -> Iterator[Project]:
822-
"""Creates a project instance pointing to a temporary working directory."""
823-
with tempfile.TemporaryDirectory(prefix='.mesonpy-', dir=os.fspath(source_dir)) as tmpdir:
824-
yield cls(source_dir, tmpdir, build_dir, meson_args, editable_verbose)
825-
826812
@functools.lru_cache()
827813
def _info(self, name: str) -> Dict[str, Any]:
828814
"""Read info from meson-info directory."""
@@ -974,18 +960,19 @@ def editable(self, directory: Path) -> pathlib.Path:
974960

975961

976962
@contextlib.contextmanager
977-
def _project(config_settings: Optional[Dict[Any, Any]]) -> Iterator[Project]:
963+
def _project(config_settings: Optional[Dict[Any, Any]] = None) -> Iterator[Project]:
978964
"""Create the project given the given config settings."""
979965

980966
settings = _validate_config_settings(config_settings or {})
981-
meson_args = {name: settings.get(f'{name}-args', []) for name in _MESON_ARGS_KEYS}
982-
983-
with Project.with_temp_working_dir(
984-
build_dir=settings.get('builddir'),
985-
meson_args=typing.cast(MesonArgs, meson_args),
986-
editable_verbose=bool(settings.get('editable-verbose'))
987-
) as project:
988-
yield project
967+
meson_args = typing.cast(MesonArgs, {name: settings.get(f'{name}-args', []) for name in _MESON_ARGS_KEYS})
968+
source_dir = os.path.curdir
969+
build_dir = settings.get('builddir')
970+
editable_verbose = bool(settings.get('editable-verbose'))
971+
972+
with contextlib.ExitStack() as ctx:
973+
if build_dir is None:
974+
build_dir = ctx.enter_context(tempfile.TemporaryDirectory(prefix='.mesonpy-', dir=source_dir))
975+
yield Project(source_dir, build_dir, meson_args, editable_verbose)
989976

990977

991978
def _parse_version_string(string: str) -> Tuple[int, ...]:

tests/test_editable.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,12 @@ def test_collect(package_complex):
6161
def test_mesonpy_meta_finder(package_complex, tmp_path):
6262
# build a package in a temporary directory
6363
mesonpy.Project(package_complex, tmp_path)
64-
build_path = tmp_path / 'build'
6564

6665
# point the meta finder to the build directory
67-
finder = _editable.MesonpyMetaFinder({'complex'}, os.fspath(build_path), ['ninja'])
66+
finder = _editable.MesonpyMetaFinder({'complex'}, os.fspath(tmp_path), ['ninja'])
6867

6968
# check repr
70-
assert repr(finder) == f'MesonpyMetaFinder({str(build_path)!r})'
69+
assert repr(finder) == f'MesonpyMetaFinder({str(tmp_path)!r})'
7170

7271
# verify that we can look up a pure module in the source directory
7372
spec = finder.find_spec('complex')
@@ -79,7 +78,7 @@ def test_mesonpy_meta_finder(package_complex, tmp_path):
7978
spec = finder.find_spec('complex.test')
8079
assert spec.name == 'complex.test'
8180
assert isinstance(spec.loader, _editable.ExtensionFileLoader)
82-
assert spec.origin == os.fspath(build_path / f'test{EXT_SUFFIX}')
81+
assert spec.origin == os.fspath(tmp_path / f'test{EXT_SUFFIX}')
8382

8483
try:
8584
# install the finder in the meta path
@@ -89,7 +88,7 @@ def test_mesonpy_meta_finder(package_complex, tmp_path):
8988
assert complex.__spec__.origin == os.fspath(package_complex / 'complex/__init__.py')
9089
assert complex.__file__ == os.fspath(package_complex / 'complex/__init__.py')
9190
import complex.test
92-
assert complex.test.__spec__.origin == os.fspath(build_path / f'test{EXT_SUFFIX}')
91+
assert complex.test.__spec__.origin == os.fspath(tmp_path / f'test{EXT_SUFFIX}')
9392
assert complex.test.answer() == 42
9493
import complex.namespace.foo
9594
assert complex.namespace.foo.__spec__.origin == os.fspath(package_complex / 'complex/namespace/foo.py')
@@ -128,7 +127,7 @@ def test_resources(tmp_path):
128127
mesonpy.Project(package_path, tmp_path)
129128

130129
# point the meta finder to the build directory
131-
finder = _editable.MesonpyMetaFinder({'simple'}, os.fspath(tmp_path / 'build'), ['ninja'])
130+
finder = _editable.MesonpyMetaFinder({'simple'}, os.fspath(tmp_path), ['ninja'])
132131

133132
# verify that we can look up resources
134133
spec = finder.find_spec('simple')
@@ -147,7 +146,7 @@ def test_importlib_resources(tmp_path):
147146
mesonpy.Project(package_path, tmp_path)
148147

149148
# point the meta finder to the build directory
150-
finder = _editable.MesonpyMetaFinder({'simple'}, os.fspath(tmp_path / 'build'), ['ninja'])
149+
finder = _editable.MesonpyMetaFinder({'simple'}, os.fspath(tmp_path), ['ninja'])
151150

152151
try:
153152
# install the finder in the meta path

tests/test_options.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@ def test_ndebug(package_purelib_and_platlib, tmp_path, args, expected):
3232
# compile a C source file (the trailing ^ is used to
3333
# specify the target that is the first output of the rule
3434
# containing the specified source file).
35-
['ninja', '-C', os.fspath(project._build_dir), '-t', 'commands', '../../plat.c^'],
35+
['ninja', '-C', os.fspath(project._build_dir), '-t', 'commands', '../plat.c^'],
3636
stdout=subprocess.PIPE, check=True).stdout
3737
assert (b'-DNDEBUG' in command) == expected

tests/test_project.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
]
3131
)
3232
def test_name(package):
33-
with chdir(package_dir / package), mesonpy.Project.with_temp_working_dir() as project:
33+
with chdir(package_dir / package), mesonpy._project() as project:
3434
assert project.name == package.replace('-', '_')
3535

3636

@@ -42,21 +42,21 @@ def test_name(package):
4242
]
4343
)
4444
def test_version(package):
45-
with chdir(package_dir / package), mesonpy.Project.with_temp_working_dir() as project:
45+
with chdir(package_dir / package), mesonpy._project() as project:
4646
assert project.version == '1.0.0'
4747

4848

4949
def test_unsupported_dynamic(package_unsupported_dynamic):
5050
with pytest.raises(mesonpy.MesonBuilderError, match='Unsupported dynamic fields: "dependencies"'):
51-
with mesonpy.Project.with_temp_working_dir():
51+
with mesonpy._project():
5252
pass
5353

5454

5555
def test_unsupported_python_version(package_unsupported_python_version):
5656
with pytest.raises(mesonpy.MesonBuilderError, match=(
5757
f'Unsupported Python version {platform.python_version()}, expected ==1.0.0'
5858
)):
59-
with mesonpy.Project.with_temp_working_dir():
59+
with mesonpy._project():
6060
pass
6161

6262

@@ -205,7 +205,7 @@ def test_invalid_build_dir(package_pure, tmp_path, mocker):
205205
meson.reset_mock()
206206

207207
# corrupting the build direcory setup is run again
208-
tmp_path.joinpath('build/meson-private/coredata.dat').unlink()
208+
tmp_path.joinpath('meson-private/coredata.dat').unlink()
209209
project = mesonpy.Project(package_pure, tmp_path)
210210
assert len(meson.call_args_list) == 1
211211
assert meson.call_args_list[0].args[1][1] == 'setup'
@@ -214,7 +214,7 @@ def test_invalid_build_dir(package_pure, tmp_path, mocker):
214214
meson.reset_mock()
215215

216216
# removing the build directory things should still work
217-
shutil.rmtree(tmp_path.joinpath('build'))
217+
shutil.rmtree(tmp_path)
218218
project = mesonpy.Project(package_pure, tmp_path)
219219
assert len(meson.call_args_list) == 1
220220
assert meson.call_args_list[0].args[1][1] == 'setup'

tests/test_wheel.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ def test_entrypoints(wheel_full_metadata):
229229

230230

231231
def test_top_level_modules(package_module_types):
232-
with mesonpy.Project.with_temp_working_dir() as project:
232+
with mesonpy._project() as project:
233233
assert set(project._wheel_builder.top_level_modules) == {
234234
'file',
235235
'package',
@@ -239,7 +239,7 @@ def test_top_level_modules(package_module_types):
239239

240240
def test_purelib_platlib_split(package_purelib_platlib_split, tmp_path):
241241
with pytest.raises(mesonpy.BuildError, match='The purelib-platlib-split package is split'):
242-
with mesonpy.Project.with_temp_working_dir() as project:
242+
with mesonpy._project() as project:
243243
project.wheel(tmp_path)
244244

245245

0 commit comments

Comments
 (0)