Skip to content

Commit da1c2e0

Browse files
dnicolodirgommers
authored andcommitted
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 4b41aa4 commit da1c2e0

File tree

5 files changed

+54
-68
lines changed

5 files changed

+54
-68
lines changed

mesonpy/__init__.py

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

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

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

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

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

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

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

527530
return wheel_file
528531

@@ -639,16 +642,13 @@ class Project():
639642
def __init__( # noqa: C901
640643
self,
641644
source_dir: Path,
642-
working_dir: Path,
643-
build_dir: Optional[Path] = None,
645+
build_dir: Path,
644646
meson_args: Optional[MesonArgs] = None,
645647
editable_verbose: bool = False,
646648
) -> None:
647649
self._source_dir = pathlib.Path(source_dir).absolute()
648-
self._working_dir = pathlib.Path(working_dir).absolute()
649-
self._build_dir = pathlib.Path(build_dir).absolute() if build_dir else (self._working_dir / 'build')
650+
self._build_dir = pathlib.Path(build_dir).absolute()
650651
self._editable_verbose = editable_verbose
651-
self._install_dir = self._working_dir / 'install'
652652
self._meson_native_file = self._build_dir / 'meson-python-native-file.ini'
653653
self._meson_cross_file = self._build_dir / 'meson-python-cross-file.ini'
654654
self._meson_args: MesonArgs = collections.defaultdict(list)
@@ -663,7 +663,6 @@ def __init__( # noqa: C901
663663

664664
# make sure the build dir exists
665665
self._build_dir.mkdir(exist_ok=True, parents=True)
666-
self._install_dir.mkdir(exist_ok=True, parents=True)
667666

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

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

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

985971

986972
@contextlib.contextmanager
987-
def _project(config_settings: Optional[Dict[Any, Any]]) -> Iterator[Project]:
973+
def _project(config_settings: Optional[Dict[Any, Any]] = None) -> Iterator[Project]:
988974
"""Create the project given the given config settings."""
989975

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

1000987

1001988
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: 3 additions & 3 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

@@ -303,7 +303,7 @@ def test_limited_api(wheel_limited_api):
303303
@pytest.mark.skipif(MESON_VERSION < (1, 2, 99), reason='Meson version too old')
304304
def test_limited_api_bad(package_limited_api, tmp_path):
305305
with pytest.raises(mesonpy.BuildError, match='The package declares compatibility with Python limited API but '):
306-
with mesonpy.Project.with_temp_working_dir(meson_args={'setup': ['-Dextra=true']}) as project:
306+
with mesonpy._project({'setup-args': ['-Dextra=true']}) as project:
307307
project.wheel(tmp_path)
308308

309309

0 commit comments

Comments
 (0)