python: Add platlibdir and purelibdir options

pull/9357/merge
Xavier Claessens 3 years ago committed by Eli Schwartz
parent 5316c7df62
commit 329d111709
  1. 20
      docs/markdown/Builtin-options.md
  2. 6
      docs/markdown/Python-module.md
  3. 12
      docs/markdown/snippets/python_install_path.md
  4. 8
      mesonbuild/coredata.py
  5. 2
      mesonbuild/mesonlib/universal.py
  6. 17
      mesonbuild/modules/python.py
  7. 10
      test cases/python/7 install path/meson.build
  8. 6
      test cases/python/7 install path/test.json
  9. 0
      test cases/python/7 install path/test.py
  10. 7
      unittests/datatests.py

@ -260,3 +260,23 @@ The value is overridden in this order:
- Value from command line if set - Value from command line if set
Since 0.56.0 `warning_level` can also be defined per subproject. Since 0.56.0 `warning_level` can also be defined per subproject.
## Module options
Some Meson modules have built-in options. They can be set by prefixing the option
name with the module name: `-D<module>.<option>=<value>` (e.g. `-Dpython.platlibdir=/foo`).
### Python module
| Option | Default value | Possible values | Description |
| ------ | ------------- | --------------- | ----------- |
| platlibdir | | Directory path | Directory for site-specific, platform-specific files (Since 0.60.0) |
| purelibdir | | Directory path | Directory for site-specific, non-platform-specific files (Since 0.60.0) |
*Since 0.60.0* `python.platlibdir` and `python.purelibdir` options are used by
python module methods `python.install_sources()` and `python.get_install_dir()`.
By default Meson tries to detect the correct installation path, but make them
relative to the installation `prefix`, which will often result in installed python
modules to not be found by the interpreter unless `prefix` is `/usr` on Linux,
or for example `C:\Python39` on Windows. These options can be absolute paths
outside of `prefix`.

@ -128,6 +128,9 @@ Install actual python sources (`.py`).
All positional and keyword arguments are the same as for All positional and keyword arguments are the same as for
[[install_data]], with the addition of the following: [[install_data]], with the addition of the following:
*Since 0.60.0* `python.platlibdir` and `python.purelibdir` options can be used
to control the default installation path. See [Python module options](Builtin-options.md#python-module).
- `pure`: On some platforms, architecture independent files are - `pure`: On some platforms, architecture independent files are
expected to be placed in a separate directory. However, if the expected to be placed in a separate directory. However, if the
python sources should be installed alongside an extension module python sources should be installed alongside an extension module
@ -154,6 +157,9 @@ directly, for example when using [[configure_file]].
This function accepts no arguments, its keyword arguments are the same This function accepts no arguments, its keyword arguments are the same
as [][`install_sources()`]. as [][`install_sources()`].
*Since 0.60.0* `python.platlibdir` and `python.purelibdir` options can be used
to control the default installation path. See [Python module options](Builtin-options.md#python-module).
**Returns**: A string **Returns**: A string
#### `language_version()` #### `language_version()`

@ -0,0 +1,12 @@
## Override python installation paths
The `python` module now has options to control where modules are installed:
- python.platlibdir: Directory for site-specific, platform-specific files.
- python.purelibdir: Directory for site-specific, non-platform-specific files.
Those options are used by python module methods `python.install_sources()` and
`python.get_install_dir()`. By default Meson tries to detect the correct installation
path, but make them relative to the installation `prefix`, which will often result
in installed python modules to not be found by the interpreter unless `prefix`
is `/usr` on Linux, or for example `C:\Python39` on Windows. These new options
can be absolute paths outside of `prefix`.

@ -1188,6 +1188,12 @@ BUILTIN_CORE_OPTIONS: 'KeyedOptionDictType' = OrderedDict([
(OptionKey('werror'), BuiltinOption(UserBooleanOption, 'Treat warnings as errors', False, yielding=False)), (OptionKey('werror'), BuiltinOption(UserBooleanOption, 'Treat warnings as errors', False, yielding=False)),
(OptionKey('wrap_mode'), BuiltinOption(UserComboOption, 'Wrap mode', 'default', choices=['default', 'nofallback', 'nodownload', 'forcefallback', 'nopromote'])), (OptionKey('wrap_mode'), BuiltinOption(UserComboOption, 'Wrap mode', 'default', choices=['default', 'nofallback', 'nodownload', 'forcefallback', 'nopromote'])),
(OptionKey('force_fallback_for'), BuiltinOption(UserArrayOption, 'Force fallback for those subprojects', [])), (OptionKey('force_fallback_for'), BuiltinOption(UserArrayOption, 'Force fallback for those subprojects', [])),
# Python module
(OptionKey('platlibdir', module='python'),
BuiltinOption(UserStringOption, 'Directory for site-specific, platform-specific files.', '')),
(OptionKey('purelibdir', module='python'),
BuiltinOption(UserStringOption, 'Directory for site-specific, non-platform-specific files.', '')),
]) ])
BUILTIN_OPTIONS = OrderedDict(chain(BUILTIN_DIR_OPTIONS.items(), BUILTIN_CORE_OPTIONS.items())) BUILTIN_OPTIONS = OrderedDict(chain(BUILTIN_DIR_OPTIONS.items(), BUILTIN_CORE_OPTIONS.items()))
@ -1203,6 +1209,8 @@ BULITIN_DIR_NOPREFIX_OPTIONS: T.Dict[OptionKey, T.Dict[str, str]] = {
OptionKey('sysconfdir'): {'/usr': '/etc'}, OptionKey('sysconfdir'): {'/usr': '/etc'},
OptionKey('localstatedir'): {'/usr': '/var', '/usr/local': '/var/local'}, OptionKey('localstatedir'): {'/usr': '/var', '/usr/local': '/var/local'},
OptionKey('sharedstatedir'): {'/usr': '/var/lib', '/usr/local': '/var/local/lib'}, OptionKey('sharedstatedir'): {'/usr': '/var/lib', '/usr/local': '/var/local/lib'},
OptionKey('platlibdir', module='python'): {},
OptionKey('purelibdir', module='python'): {},
} }
FORBIDDEN_TARGET_NAMES = {'clean': None, FORBIDDEN_TARGET_NAMES = {'clean': None,

@ -2100,7 +2100,7 @@ class OptionKey:
def __hash__(self) -> int: def __hash__(self) -> int:
return self._hash return self._hash
def _to_tuple(self): def _to_tuple(self) -> T.Tuple[str, OptionType, str, str, MachineChoice, str]:
return (self.subproject, self.type, self.lang or '', self.module or '', self.machine, self.name) return (self.subproject, self.type, self.lang or '', self.module or '', self.machine, self.name)
def __eq__(self, other: object) -> bool: def __eq__(self, other: object) -> bool:

@ -374,7 +374,7 @@ class PythonExternalProgram(ExternalProgram):
return mesonlib.version_compare(version, '>= 3.0') return mesonlib.version_compare(version, '>= 3.0')
return True return True
def sanity(self) -> bool: def sanity(self, state: T.Optional['ModuleState'] = None) -> bool:
# Sanity check, we expect to have something that at least quacks in tune # Sanity check, we expect to have something that at least quacks in tune
cmd = self.get_command() + ['-c', INTROSPECT_COMMAND] cmd = self.get_command() + ['-c', INTROSPECT_COMMAND]
p, stdout, stderr = mesonlib.Popen_safe(cmd) p, stdout, stderr = mesonlib.Popen_safe(cmd)
@ -392,19 +392,24 @@ class PythonExternalProgram(ExternalProgram):
variables = info['variables'] variables = info['variables']
info['suffix'] = variables.get('EXT_SUFFIX') or variables.get('SO') or variables.get('.so') info['suffix'] = variables.get('EXT_SUFFIX') or variables.get('SO') or variables.get('.so')
self.info = T.cast('PythonIntrospectionDict', info) self.info = T.cast('PythonIntrospectionDict', info)
self.platlib = self._get_path('platlib') self.platlib = self._get_path(state, 'platlib')
self.purelib = self._get_path('purelib') self.purelib = self._get_path(state, 'purelib')
return True return True
else: else:
return False return False
def _get_path(self, key: str) -> None: def _get_path(self, state: T.Optional['ModuleState'], key: str) -> None:
if state:
value = state.get_option(f'{key}dir', module='python')
if value:
return value
user_dir = str(Path.home()) user_dir = str(Path.home())
sys_paths = self.info['sys_paths'] sys_paths = self.info['sys_paths']
rel_path = self.info['install_paths'][key][1:] rel_path = self.info['install_paths'][key][1:]
if not any(p.endswith(rel_path) for p in sys_paths if not p.startswith(user_dir)): if not any(p.endswith(rel_path) for p in sys_paths if not p.startswith(user_dir)):
mlog.warning('Broken python installation detected. Python files', mlog.warning('Broken python installation detected. Python files',
'installed by Meson might not be found by python interpreter.', 'installed by Meson might not be found by python interpreter.\n',
f'This warning can be avoided by setting "python.{key}dir" option.',
once=True) once=True)
return rel_path return rel_path
@ -697,7 +702,7 @@ class PythonModule(ExtensionModule):
raise mesonlib.MesonException('{} is missing modules: {}'.format(name_or_path or 'python', ', '.join(missing_modules))) raise mesonlib.MesonException('{} is missing modules: {}'.format(name_or_path or 'python', ', '.join(missing_modules)))
return NonExistingExternalProgram() return NonExistingExternalProgram()
else: else:
sane = python.sanity() sane = python.sanity(state)
if sane: if sane:
return python return python

@ -0,0 +1,10 @@
project('install path',
default_options: [
'python.purelibdir=/pure',
'python.platlibdir=/plat'
]
)
py = import('python').find_installation()
py.install_sources('test.py')
py.install_sources('test.py', pure: false)

@ -0,0 +1,6 @@
{
"installed": [
{"type": "file", "file": "plat/test.py"},
{"type": "file", "file": "pure/test.py"}
]
}

@ -126,7 +126,8 @@ class DataTests(unittest.TestCase):
subsections = tee(re.finditer(r"^### (.+)$", content, re.MULTILINE)) subsections = tee(re.finditer(r"^### (.+)$", content, re.MULTILINE))
subcontent1 = self._get_section_content("Directories", subsections[0], content) subcontent1 = self._get_section_content("Directories", subsections[0], content)
subcontent2 = self._get_section_content("Core options", subsections[1], content) subcontent2 = self._get_section_content("Core options", subsections[1], content)
for subcontent in (subcontent1, subcontent2): subcontent3 = self._get_section_content("Module options", sections, md)
for subcontent in (subcontent1, subcontent2, subcontent3):
# Find the option names # Find the option names
options = set() options = set()
# Match either a table row or a table heading separator: | ------ | # Match either a table row or a table heading separator: | ------ |
@ -145,8 +146,8 @@ class DataTests(unittest.TestCase):
found_entries |= options found_entries |= options
self.assertEqual(found_entries, { self.assertEqual(found_entries, {
*(str(k) for k in mesonbuild.coredata.BUILTIN_OPTIONS), *(str(k.evolve(module=None)) for k in mesonbuild.coredata.BUILTIN_OPTIONS),
*(str(k) for k in mesonbuild.coredata.BUILTIN_OPTIONS_PER_MACHINE), *(str(k.evolve(module=None)) for k in mesonbuild.coredata.BUILTIN_OPTIONS_PER_MACHINE),
}) })
# Check that `buildtype` table inside `Core options` matches how # Check that `buildtype` table inside `Core options` matches how

Loading…
Cancel
Save