diff options
author | Luke Drummond <ldrumm@rtps.co> | 2020-11-03 20:28:04 +0000 |
---|---|---|
committer | Jussi Pakkanen <jpakkane@gmail.com> | 2021-02-04 17:34:11 +0000 |
commit | 46e3480f7c675b140ca4496c658069696daa176d (patch) | |
tree | 7968ab2078864ecf71212cf291abb462d081596f /mesonbuild/modules/fs.py | |
parent | 95c079071118abc9078b0931ee86f51b1b4f8e05 (diff) | |
download | meson-46e3480f7c675b140ca4496c658069696daa176d.zip meson-46e3480f7c675b140ca4496c658069696daa176d.tar.gz meson-46e3480f7c675b140ca4496c658069696daa176d.tar.bz2 |
Introduce `fs.read` to read a file as a string
Following #7890, this patch introduces the ability to read the contents
of a file to the fs module.
This patch introduces the ability to read files at configure time, but
has some restrictions:
- binary files are not supported (I don't think this will prove a
problem, and if people are wanting to do something with binary
files, they should probably be shelling out to their own script).
- Only files outside the build directory allowed. This limitation
should prevent build loops.
Given that reading an arbitrary file at configure time can affect the
configuration in almost arbitrary ways, meson should force a reconfigure
when the given file changes. This is non-configurable, but this can
easily be changed with a future keyword argument.
Diffstat (limited to 'mesonbuild/modules/fs.py')
-rw-r--r-- | mesonbuild/modules/fs.py | 65 |
1 files changed, 64 insertions, 1 deletions
diff --git a/mesonbuild/modules/fs.py b/mesonbuild/modules/fs.py index 2ff256b..44986f8 100644 --- a/mesonbuild/modules/fs.py +++ b/mesonbuild/modules/fs.py @@ -14,18 +14,25 @@ import typing as T import hashlib +import os from pathlib import Path, PurePath, PureWindowsPath from .. import mlog from . import ExtensionModule from . import ModuleReturnValue -from ..mesonlib import MesonException +from ..mesonlib import ( + File, + FileOrString, + MesonException, + path_is_in_root, +) from ..interpreterbase import FeatureNew from ..interpreterbase import stringArgs, noKwargs if T.TYPE_CHECKING: from ..interpreter import Interpreter, ModuleState + class FSModule(ExtensionModule): def __init__(self, interpreter: 'Interpreter') -> None: @@ -193,5 +200,61 @@ class FSModule(ExtensionModule): new = original.stem return ModuleReturnValue(str(new), []) + #@permittedKwargs({'encoding'}) + @FeatureNew('fs.read', '0.57.0') + def read( + self, + state: 'ModuleState', + args: T.Sequence['FileOrString'], + kwargs: T.Dict[str, T.Any] + ) -> ModuleReturnValue: + ''' + Read a file from the source tree and return its value as a decoded + string. If the encoding is not specified, the file is assumed to be + utf-8 encoded. Paths must be relative by default (to prevent accidents) + and are forbidden to be read from the build directory (to prevent build + loops) + ''' + if len(args) != 1: + raise MesonException('expected single positional <path> arg') + + path = args[0] + if not isinstance(path, (str, File)): + raise MesonException( + '<path> positional argument must be string or files() object') + + encoding = kwargs.get('encoding', 'utf-8') + if not isinstance(encoding, str): + raise MesonException('`encoding` parameter must be a string') + + src_dir = self.interpreter.environment.source_dir + sub_dir = self.interpreter.subdir + build_dir = self.interpreter.environment.get_build_dir() + + if isinstance(path, File): + if path.is_built: + raise MesonException( + 'fs.read_file does not accept built files() objects') + path = os.path.join(src_dir, path.relative_name()) + else: + if sub_dir: + src_dir = os.path.join(src_dir, sub_dir) + path = os.path.join(src_dir, path) + + path = os.path.abspath(path) + if path_is_in_root(Path(path), Path(build_dir), resolve=True): + raise MesonException('path must not be in the build tree') + try: + with open(path, 'r', encoding=encoding) as f: + data = f.read() + except UnicodeDecodeError: + raise MesonException(f'decoding failed for {path}') + # Reconfigure when this file changes as it can contain data used by any + # part of the build configuration (e.g. `project(..., version: + # fs.read_file('VERSION')` or `configure_file(...)` + self.interpreter.add_build_def_file(path) + return ModuleReturnValue(data, []) + + def initialize(*args: T.Any, **kwargs: T.Any) -> FSModule: return FSModule(*args, **kwargs) |