aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/markdown/Fs-module.md41
-rw-r--r--mesonbuild/modules/fs.py50
-rw-r--r--test cases/common/227 fs module/meson.build22
3 files changed, 101 insertions, 12 deletions
diff --git a/docs/markdown/Fs-module.md b/docs/markdown/Fs-module.md
index 53bf960..36b4c4a 100644
--- a/docs/markdown/Fs-module.md
+++ b/docs/markdown/Fs-module.md
@@ -10,6 +10,8 @@ current `meson.build` file is.
If specified, a leading `~` is expanded to the user home directory.
+Where possible, symlinks and parent directory notation are resolved to an absolute path.
+
### exists
Takes a single string argument and returns true if an entity with that
@@ -19,13 +21,12 @@ special entry such as a device node.
### is_dir
Takes a single string argument and returns true if a directory with
-that name exists on the file system. This method follows symbolic
-links.
+that name exists on the file system.
### is_file
Takes a single string argument and returns true if an file with that
-name exists on the file system. This method follows symbolic links.
+name exists on the file system.
### is_symlink
@@ -34,6 +35,23 @@ by the string is a symbolic link.
## File Parameters
+### is_absolute
+
+Return a boolean indicating if the path string specified is absolute for this computer, WITHOUT expanding `~`.
+
+Examples:
+
+```meson
+fs.is_absolute('~') # false unless you literally have a path with string name `~`
+
+home = fs.expanduser('~')
+fs.is_absolute(home) # true
+
+fs.is_absolute(home / 'foo') # true, even if ~/foo doesn't exist
+
+fs.is_absolute('foo/bar') # false, even if ./foo/bar exists
+```
+
### hash
The `fs.hash(filename, hash_algorithm)` method returns a string containing
@@ -44,7 +62,6 @@ md5, sha1, sha224, sha256, sha384, sha512.
### size
The `fs.size(filename)` method returns the size of the file in integer bytes.
-Symlinks will be resolved if possible.
### is_samepath
@@ -79,7 +96,21 @@ fs.is_samepath(p, s) # false
## Filename modification
-The files need not actually exist yet for this method, as it's just string manipulation.
+The files need not actually exist yet for these methods, as they are just string manipulation.
+
+### as_posix
+
+`fs.as_posix(path)` assumes a Windows path, even if on a Unix-like system.
+Thus, all `'\'` or `'\\'` are turned to '/', even if you meant to escape a character.
+
+Examples
+
+```meson
+fs.as_posix('\\') == '/' # true
+fs.as_posix('\\\\') == '/' # true
+
+fs.as_posix('foo\\bar/baz') == 'foo/bar/baz' # true
+```
### replace_suffix
diff --git a/mesonbuild/modules/fs.py b/mesonbuild/modules/fs.py
index 523ad2b..9b2cb8b 100644
--- a/mesonbuild/modules/fs.py
+++ b/mesonbuild/modules/fs.py
@@ -31,18 +31,52 @@ class FSModule(ExtensionModule):
super().__init__(interpreter)
self.snippets.add('generate_dub_file')
+ def _absolute_dir(self, state: 'ModuleState', arg: str) -> Path:
+ """
+ make an absolute path from a relative path, WITHOUT resolving symlinks
+ """
+ return Path(state.source_root) / state.subdir / Path(arg).expanduser()
+
def _resolve_dir(self, state: 'ModuleState', arg: str) -> Path:
"""
- resolves (makes absolute) a directory relative to calling meson.build,
+ resolves symlinks and makes absolute a directory relative to calling meson.build,
if not already absolute
"""
- return Path(state.source_root) / state.subdir / Path(arg).expanduser()
+ path = self._absolute_dir(state, arg)
+ try:
+ # accomodate unresolvable paths e.g. symlink loops
+ path = path.resolve()
+ except Exception:
+ # return the best we could do
+ pass
+ return path
def _check(self, check: str, state: 'ModuleState', args: T.Sequence[str]) -> ModuleReturnValue:
if len(args) != 1:
raise MesonException('fs.{} takes exactly one argument.'.format(check))
test_file = self._resolve_dir(state, args[0])
- return ModuleReturnValue(getattr(test_file, check)(), [])
+ val = getattr(test_file, check)()
+ if isinstance(val, Path):
+ val = str(val)
+ return ModuleReturnValue(val, [])
+
+ @stringArgs
+ @noKwargs
+ def is_absolute(self, state: 'ModuleState', args: T.Sequence[str], kwargs: dict) -> ModuleReturnValue:
+ if len(args) != 1:
+ raise MesonException('fs.is_absolute takes exactly one argument.')
+ return ModuleReturnValue(PurePath(args[0]).is_absolute(), [])
+
+ @stringArgs
+ @noKwargs
+ def as_posix(self, state: 'ModuleState', args: T.Sequence[str], kwargs: dict) -> ModuleReturnValue:
+ """
+ this function assumes you are passing a Windows path, even if on a Unix-like system
+ and so ALL '\' are turned to '/', even if you meant to escape a character
+ """
+ if len(args) != 1:
+ raise MesonException('fs.as_posix takes exactly one argument.')
+ return ModuleReturnValue(PureWindowsPath(args[0]).as_posix(), [])
@stringArgs
@noKwargs
@@ -52,7 +86,9 @@ class FSModule(ExtensionModule):
@stringArgs
@noKwargs
def is_symlink(self, state: 'ModuleState', args: T.Sequence[str], kwargs: dict) -> ModuleReturnValue:
- return self._check('is_symlink', state, args)
+ if len(args) != 1:
+ raise MesonException('fs.is_symlink takes exactly one argument.')
+ return ModuleReturnValue(self._absolute_dir(state, args[0]).is_symlink(), [])
@stringArgs
@noKwargs
@@ -68,7 +104,7 @@ class FSModule(ExtensionModule):
@noKwargs
def hash(self, state: 'ModuleState', args: T.Sequence[str], kwargs: dict) -> ModuleReturnValue:
if len(args) != 2:
- raise MesonException('method takes exactly two arguments.')
+ raise MesonException('fs.hash takes exactly two arguments.')
file = self._resolve_dir(state, args[0])
if not file.is_file():
raise MesonException('{} is not a file and therefore cannot be hashed'.format(file))
@@ -84,7 +120,7 @@ class FSModule(ExtensionModule):
@noKwargs
def size(self, state: 'ModuleState', args: T.Sequence[str], kwargs: dict) -> ModuleReturnValue:
if len(args) != 1:
- raise MesonException('method takes exactly one argument.')
+ raise MesonException('fs.size takes exactly one argument.')
file = self._resolve_dir(state, args[0])
if not file.is_file():
raise MesonException('{} is not a file and therefore cannot be sized'.format(file))
@@ -113,7 +149,7 @@ class FSModule(ExtensionModule):
@noKwargs
def replace_suffix(self, state: 'ModuleState', args: T.Sequence[str], kwargs: dict) -> ModuleReturnValue:
if len(args) != 2:
- raise MesonException('method takes exactly two arguments.')
+ raise MesonException('fs.replace_suffix takes exactly two arguments.')
original = PurePath(args[0])
new = original.with_suffix(args[1])
return ModuleReturnValue(str(new), [])
diff --git a/test cases/common/227 fs module/meson.build b/test cases/common/227 fs module/meson.build
index 25778af..670ffed 100644
--- a/test cases/common/227 fs module/meson.build
+++ b/test cases/common/227 fs module/meson.build
@@ -33,6 +33,28 @@ assert(not fs.is_dir('nonexisting'), 'Bad path detected as a dir.')
assert(fs.is_dir('~'), 'expanduser not working')
assert(not fs.is_file('~'), 'expanduser not working')
+# -- as_posix
+assert(fs.as_posix('/') == '/', 'as_posix idempotent')
+assert(fs.as_posix('\\') == '/', 'as_posix simple')
+assert(fs.as_posix('\\\\') == '/', 'as_posix simple')
+assert(fs.as_posix('foo\\bar/baz') == 'foo/bar/baz', 'as_posix mixed slash')
+
+# -- is_absolute
+winabs = 'q:/foo'
+unixabs = '/foo'
+if is_windows
+ assert(fs.is_absolute(winabs), 'is_absolute windows not detected')
+ assert(not fs.is_absolute(unixabs), 'is_absolute unix false positive')
+else
+ assert(fs.is_absolute(unixabs), 'is_absolute unix not detected')
+ assert(not fs.is_absolute(winabs), 'is_absolute windows false positive')
+endif
+
+# -- replace_suffix
+
+original = 'foo'
+assert(fs.replace_suffix(original, '') == original, 'replace_suffix idempotent')
+
original = 'foo.txt'
new = fs.replace_suffix(original, '.ini')
assert(new == 'foo.ini', 'replace_suffix failed')