Skip to content

Commit f97ff4d

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 addressed in later commits.
1 parent 53fbcdd commit f97ff4d

File tree

5 files changed

+53
-67
lines changed

5 files changed

+53
-67
lines changed

mesonpy/__init__.py

Lines changed: 35 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -498,30 +498,33 @@ def _wheel_write_metadata(self, whl: mesonpy._wheelfile.WheelFile) -> None:
498498
def build(self, directory: Path) -> pathlib.Path:
499499
# ensure project is built
500500
self._project.build()
501-
# install the project
502-
self._project.install()
503501

504-
wheel_file = pathlib.Path(directory, f'{self.name}.whl')
505-
with mesonpy._wheelfile.WheelFile(wheel_file, 'w') as whl:
506-
self._wheel_write_metadata(whl)
502+
# install project in temporary destination directory
503+
with tempfile.TemporaryDirectory() as destdir:
504+
self._project.install(destdir)
507505

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

510-
root = 'purelib' if self.is_pure else 'platlib'
508+
with mesonpy._wheelfile.WheelFile(wheel_file, 'w') as whl:
509+
self._wheel_write_metadata(whl)
511510

512-
for path, entries in self._wheel_files.items():
513-
for dst, src in entries:
514-
counter.update(src)
511+
with mesonpy._util.cli_counter(sum(len(x) for x in self._wheel_files.values())) as counter:
515512

516-
if path == root:
517-
pass
518-
elif path == 'mesonpy-libs':
519-
# custom installation path for bundled libraries
520-
dst = pathlib.Path(f'.{self._project.name}.mesonpy.libs', dst)
521-
else:
522-
dst = pathlib.Path(self.data_dir, path, dst)
513+
root = 'purelib' if self.is_pure else 'platlib'
514+
515+
for path, entries in self._wheel_files.items():
516+
for dst, src in entries:
517+
counter.update(src)
523518

524-
self._install_path(whl, src, dst)
519+
if path == root:
520+
pass
521+
elif path == 'mesonpy-libs':
522+
# custom installation path for bundled libraries
523+
dst = pathlib.Path(f'.{self._project.name}.mesonpy.libs', dst)
524+
else:
525+
dst = pathlib.Path(self.data_dir, path, dst)
526+
527+
self._install_path(whl, src, dst)
525528

526529
return wheel_file
527530

@@ -638,16 +641,13 @@ class Project():
638641
def __init__( # noqa: C901
639642
self,
640643
source_dir: Path,
641-
working_dir: Path,
642-
build_dir: Optional[Path] = None,
644+
build_dir: Path,
643645
meson_args: Optional[MesonArgs] = None,
644646
editable_verbose: bool = False,
645647
) -> None:
646648
self._source_dir = pathlib.Path(source_dir).absolute()
647-
self._working_dir = pathlib.Path(working_dir).absolute()
648-
self._build_dir = pathlib.Path(build_dir).absolute() if build_dir else (self._working_dir / 'build')
649+
self._build_dir = pathlib.Path(build_dir).absolute()
649650
self._editable_verbose = editable_verbose
650-
self._install_dir = self._working_dir / 'install'
651651
self._meson_native_file = self._build_dir / 'meson-python-native-file.ini'
652652
self._meson_cross_file = self._build_dir / 'meson-python-cross-file.ini'
653653
self._meson_args: MesonArgs = collections.defaultdict(list)
@@ -662,7 +662,6 @@ def __init__( # noqa: C901
662662

663663
# make sure the build dir exists
664664
self._build_dir.mkdir(exist_ok=True, parents=True)
665-
self._install_dir.mkdir(exist_ok=True, parents=True)
666665

667666
# setuptools-like ARCHFLAGS environment variable support
668667
if sysconfig.get_platform().startswith('macosx-'):
@@ -818,24 +817,11 @@ def build(self) -> None:
818817
"""Build the Meson project."""
819818
self._run(self._build_command)
820819

821-
def install(self) -> None:
820+
def install(self, destdir: Path) -> None:
822821
"""Install the Meson project."""
823-
destdir = os.fspath(self._install_dir)
822+
destdir = os.fspath(destdir)
824823
self._run(['meson', 'install', '--quiet', '--no-rebuild', '--destdir', destdir, *self._meson_args['install']])
825824

826-
@classmethod
827-
@contextlib.contextmanager
828-
def with_temp_working_dir(
829-
cls,
830-
source_dir: Path = os.path.curdir,
831-
build_dir: Optional[Path] = None,
832-
meson_args: Optional[MesonArgs] = None,
833-
editable_verbose: bool = False,
834-
) -> Iterator[Project]:
835-
"""Creates a project instance pointing to a temporary working directory."""
836-
with tempfile.TemporaryDirectory(prefix='.mesonpy-', dir=os.fspath(source_dir)) as tmpdir:
837-
yield cls(source_dir, tmpdir, build_dir, meson_args, editable_verbose)
838-
839825
@functools.lru_cache()
840826
def _info(self, name: str) -> Any:
841827
"""Read info from meson-info directory."""
@@ -983,18 +969,19 @@ def editable(self, directory: Path) -> pathlib.Path:
983969

984970

985971
@contextlib.contextmanager
986-
def _project(config_settings: Optional[Dict[Any, Any]]) -> Iterator[Project]:
972+
def _project(config_settings: Optional[Dict[Any, Any]] = None) -> Iterator[Project]:
987973
"""Create the project given the given config settings."""
988974

989975
settings = _validate_config_settings(config_settings or {})
990-
meson_args = {name: settings.get(f'{name}-args', []) for name in _MESON_ARGS_KEYS}
991-
992-
with Project.with_temp_working_dir(
993-
build_dir=settings.get('builddir'),
994-
meson_args=typing.cast(MesonArgs, meson_args),
995-
editable_verbose=bool(settings.get('editable-verbose'))
996-
) as project:
997-
yield project
976+
meson_args = typing.cast(MesonArgs, {name: settings.get(f'{name}-args', []) for name in _MESON_ARGS_KEYS})
977+
source_dir = os.path.curdir
978+
build_dir = settings.get('builddir')
979+
editable_verbose = bool(settings.get('editable-verbose'))
980+
981+
with contextlib.ExitStack() as ctx:
982+
if build_dir is None:
983+
build_dir = ctx.enter_context(tempfile.TemporaryDirectory(prefix='.mesonpy-', dir=source_dir))
984+
yield Project(source_dir, build_dir, meson_args, editable_verbose)
998985

999986

1000987
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: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
]
3232
)
3333
def test_name(package):
34-
with chdir(package_dir / package), mesonpy.Project.with_temp_working_dir() as project:
34+
with chdir(package_dir / package), mesonpy._project() as project:
3535
assert project.name == package.replace('-', '_')
3636

3737

@@ -43,37 +43,37 @@ def test_name(package):
4343
]
4444
)
4545
def test_version(package):
46-
with chdir(package_dir / package), mesonpy.Project.with_temp_working_dir() as project:
46+
with chdir(package_dir / package), mesonpy._project() as project:
4747
assert project.version == '1.0.0'
4848

4949

5050
def test_unsupported_dynamic(package_unsupported_dynamic):
5151
with pytest.raises(pyproject_metadata.ConfigurationError, match='Unsupported dynamic fields: "dependencies"'):
52-
with mesonpy.Project.with_temp_working_dir():
52+
with mesonpy._project():
5353
pass
5454

5555

5656
def test_unsupported_python_version(package_unsupported_python_version):
5757
with pytest.raises(mesonpy.MesonBuilderError, match='Package requires Python version ==1.0.0'):
58-
with mesonpy.Project.with_temp_working_dir():
58+
with mesonpy._project():
5959
pass
6060

6161

6262
def test_missing_version(package_missing_version):
6363
with pytest.raises(pyproject_metadata.ConfigurationError, match='Required "project.version" field is missing'):
64-
with mesonpy.Project.with_temp_working_dir():
64+
with mesonpy._project():
6565
pass
6666

6767

6868
def test_missing_meson_version(package_missing_meson_version):
6969
with pytest.raises(pyproject_metadata.ConfigurationError, match='Section "project" missing in pyproject.toml'):
70-
with mesonpy.Project.with_temp_working_dir():
70+
with mesonpy._project():
7171
pass
7272

7373

7474
def test_missing_dynamic_version(package_missing_dynamic_version):
7575
with pytest.raises(pyproject_metadata.ConfigurationError, match='Field "version" declared as dynamic but'):
76-
with mesonpy.Project.with_temp_working_dir():
76+
with mesonpy._project():
7777
pass
7878

7979

@@ -222,7 +222,7 @@ def test_invalid_build_dir(package_pure, tmp_path, mocker):
222222
meson.reset_mock()
223223

224224
# corrupting the build direcory setup is run again
225-
tmp_path.joinpath('build/meson-private/coredata.dat').unlink()
225+
tmp_path.joinpath('meson-private/coredata.dat').unlink()
226226
project = mesonpy.Project(package_pure, tmp_path)
227227
assert len(meson.call_args_list) == 1
228228
assert meson.call_args_list[0].args[1][1] == 'setup'
@@ -231,7 +231,7 @@ def test_invalid_build_dir(package_pure, tmp_path, mocker):
231231
meson.reset_mock()
232232

233233
# removing the build directory things should still work
234-
shutil.rmtree(tmp_path.joinpath('build'))
234+
shutil.rmtree(tmp_path)
235235
project = mesonpy.Project(package_pure, tmp_path)
236236
assert len(meson.call_args_list) == 1
237237
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
@@ -235,7 +235,7 @@ def test_entrypoints(wheel_full_metadata):
235235

236236

237237
def test_top_level_modules(package_module_types):
238-
with mesonpy.Project.with_temp_working_dir() as project:
238+
with mesonpy._project() as project:
239239
assert set(project._wheel_builder.top_level_modules) == {
240240
'file',
241241
'package',
@@ -245,7 +245,7 @@ def test_top_level_modules(package_module_types):
245245

246246
def test_purelib_platlib_split(package_purelib_platlib_split, tmp_path):
247247
with pytest.raises(mesonpy.BuildError, match='The purelib-platlib-split package is split'):
248-
with mesonpy.Project.with_temp_working_dir() as project:
248+
with mesonpy._project() as project:
249249
project.wheel(tmp_path)
250250

251251

0 commit comments

Comments
 (0)