Skip to content

Commit 8a7d119

Browse files
committed
fix: allow command sequence for python binary
Improves behavior to allow specifying a command sequence for python binary in native files.
1 parent f004d10 commit 8a7d119

6 files changed

Lines changed: 70 additions & 18 deletions

File tree

‎mesonbuild/modules/python.py‎

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ class PythonModule(ExtensionModule):
407407

408408
def __init__(self, interpreter: 'Interpreter') -> None:
409409
super().__init__(interpreter)
410-
self.installations: T.Dict[str, MaybePythonProg] = {}
410+
self.installations: T.Dict[T.Tuple[str, ...], MaybePythonProg] = {}
411411
self.methods.update({
412412
'find_installation': self.find_installation,
413413
})
@@ -480,7 +480,7 @@ def _get_win_pythonpath(name_or_path: str) -> T.Optional[str]:
480480
else:
481481
return None
482482

483-
def _find_installation_impl(self, state: 'ModuleState', display_name: str, name_or_path: str, required: bool) -> MaybePythonProg:
483+
def _find_installation_impl(self, state: 'ModuleState', display_name: str, name_or_path: T.Union[str, T.List[str], None], required: bool) -> MaybePythonProg:
484484
build_config = self.interpreter.environment.coredata.optstore.get_value_for(OptionKey('python.build_config'))
485485
assert isinstance(build_config, str), 'for mypy'
486486

@@ -490,19 +490,19 @@ def _find_installation_impl(self, state: 'ModuleState', display_name: str, name_
490490
tmp_python = ExternalProgram.from_entry(display_name, name_or_path)
491491
python = PythonExternalProgram(display_name, ext_prog=tmp_python, build_config_path=build_config)
492492

493+
program_name = name_or_path if isinstance(name_or_path, str) else name_or_path[0]
493494
if not python.found() and mesonlib.is_windows():
494-
pythonpath = self._get_win_pythonpath(name_or_path)
495+
pythonpath = self._get_win_pythonpath(program_name)
495496
if pythonpath is not None:
496-
name_or_path = pythonpath
497-
python = PythonExternalProgram(name_or_path)
497+
python = PythonExternalProgram(pythonpath)
498498

499499
# Last ditch effort, python2 or python3 can be named python
500500
# on various platforms, let's not give up just yet, if an executable
501501
# named python is available and has a compatible version, let's use
502502
# it
503-
if not python.found() and name_or_path in {'python2', 'python3'}:
503+
if not python.found() and program_name in {'python2', 'python3'}:
504504
tmp_python = ExternalProgram.from_entry(display_name, 'python')
505-
python = PythonExternalProgram(name_or_path, ext_prog=tmp_python, build_config_path=build_config)
505+
python = PythonExternalProgram(program_name, ext_prog=tmp_python, build_config_path=build_config)
506506

507507
if python.found():
508508
if python.sanity(state):
@@ -530,27 +530,27 @@ def find_installation(self, state: 'ModuleState', args: T.Tuple[T.Optional[str]]
530530
feature_check = FeatureNew('Passing "feature" option to find_installation', '0.48.0')
531531
disabled, required, feature = extract_required_kwarg(kwargs, state.subproject, feature_check)
532532

533-
# FIXME: this code is *full* of sharp corners. It assumes that it's
534-
# going to get a string value (or now a list of length 1), of `python2`
535-
# or `python3` which is completely nonsense. On windows the value could
533+
# FIXME: this code is *full* of sharp corners. On windows the value could
536534
# easily be `['py', '-3']`, or `['py', '-3.7']` to get a very specific
537535
# version of python. On Linux we might want a python that's not in
538536
# $PATH, or that uses a wrapper of some kind.
539537
np: T.List[str] = state.environment.lookup_binary_entry(MachineChoice.HOST, 'python') or []
540538
fallback = args[0]
541539
display_name = fallback or 'python'
542-
if not np and fallback is not None:
540+
if not np and fallback:
543541
np = [fallback]
544-
name_or_path = np[0] if np else None
542+
543+
prog_label = ' '.join(np) if np else 'python'
544+
cache_key = tuple(np)
545545

546546
if disabled:
547-
mlog.log('Program', name_or_path or 'python', 'found:', mlog.red('NO'), '(disabled by:', mlog.bold(feature), ')')
547+
mlog.log('Program', prog_label, 'found:', mlog.red('NO'), '(disabled by:', mlog.bold(feature), ')')
548548
return NonExistingExternalProgram()
549549

550-
python = self.installations.get(name_or_path)
550+
python = self.installations.get(cache_key)
551551
if not python:
552-
python = self._find_installation_impl(state, display_name, name_or_path, required)
553-
self.installations[name_or_path] = python
552+
python = self._find_installation_impl(state, display_name, np, required)
553+
self.installations[cache_key] = python
554554

555555
want_modules = kwargs['modules']
556556
found_modules: T.List[str] = []
@@ -581,11 +581,11 @@ def find_installation(self, state: 'ModuleState', args: T.Tuple[T.Optional[str]]
581581

582582
if not python.found():
583583
if required:
584-
raise mesonlib.MesonException('{} not found'.format(name_or_path or 'python'))
584+
raise mesonlib.MesonException('{} not found'.format(prog_label))
585585
return NonExistingExternalProgram(python.name)
586586
elif missing_modules:
587587
if required:
588-
raise mesonlib.MesonException('{} is missing modules: {}'.format(name_or_path or 'python', ', '.join(missing_modules)))
588+
raise mesonlib.MesonException('{} is missing modules: {}'.format(prog_label, ', '.join(missing_modules)))
589589
return NonExistingExternalProgram(python.name)
590590
else:
591591
assert isinstance(python, PythonExternalProgram), 'for mypy'
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import argparse
2+
3+
import codegen
4+
5+
6+
parser = argparse.ArgumentParser()
7+
parser.add_argument("output")
8+
args = parser.parse_args()
9+
10+
with open(args.output, "w", encoding="utf-8") as f:
11+
f.write(codegen.get_c_code())
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import textwrap
2+
3+
def get_c_code():
4+
return textwrap.dedent("""\
5+
int im_going_back_to(void) {
6+
return 505;
7+
}
8+
""")
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
project(
2+
'native command sequence',
3+
'c',
4+
default_options: ['buildtype=release'],
5+
)
6+
7+
if host_machine.system() == 'windows'
8+
error('MESON_SKIP_TEST: /usr/bin/env is not available on Windows')
9+
endif
10+
11+
py = import('python').find_installation(pure: false)
12+
13+
ct = custom_target(
14+
'ct',
15+
input : 'gen.py',
16+
output : 'ct.c',
17+
command : [py, '@INPUT@', '@OUTPUT@'],
18+
)
19+
20+
exe = executable('test_generated_code', ct, 'test_generated_code.c')
21+
22+
test('runtest', exe)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[binaries]
2+
python = ['/usr/bin/env', 'PYTHONPATH=@MESON_TEST_ROOT@/lib', 'python3']
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
int im_going_back_to(void);
2+
3+
int main(void) {
4+
if (im_going_back_to() == 505) {
5+
return 0;
6+
}
7+
8+
return 1;
9+
}

0 commit comments

Comments
 (0)