Skip to content

Commit eef3551

Browse files
committed
realpath_but_for_real_this_time
1 parent 39a9d60 commit eef3551

File tree

3 files changed

+51
-3
lines changed

3 files changed

+51
-3
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313

1414
### Changed
1515

16+
* Fixed symlink expansion for directories relative to the COMPAS installation folder, eg. `compas.DATA` when used from IronPython.
17+
* Fixed the result of `compas.__version__` on dev installs to properly include git hash.
18+
1619
### Removed
1720

1821

src/compas/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
version = LooseVersion(compas.__version__)
4747
versionstring = version.vstring.split('-')[0]
4848

49-
HERE = os.path.dirname(__file__)
49+
HERE = compas._os.realpath(os.path.dirname(__file__))
5050
"""str: Path to the location of the compas package."""
5151

5252
HOME = compas._os.absjoin(HERE, '../..')

src/compas/_os.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
Not intended to be used outside compas* packages.
55
"""
66
import os
7-
import shutil
8-
import sys
7+
import re
98
import tempfile
9+
import shutil
1010
import subprocess
11+
import sys
1112

1213
try:
1314
NotADirectoryError
@@ -16,10 +17,12 @@ class NotADirectoryError(Exception):
1617
pass
1718

1819
PY3 = sys.version_info[0] == 3
20+
SYMLINK_REGEX = re.compile(r"\n.*\<SYMLINKD\>\s(.*)\s\[(.*)\]\r")
1921

2022

2123
__all__ = [
2224
'absjoin',
25+
'realpath',
2326
'create_symlink',
2427
'create_symlinks',
2528
'remove_symlink',
@@ -252,6 +255,48 @@ def absjoin(*parts):
252255
return os.path.abspath(os.path.join(*parts))
253256

254257

258+
def realpath(path):
259+
"""Return the canonical path of the specified filename, eliminating any symbolic links encountered in the path.
260+
261+
This function uses Python's stdlib `os.path.realpath` in most cases,
262+
except when inside IronPython because (guess what?) it is broken and
263+
doesn't really eliminate sym links, so, we fallback to a different
264+
way to identifying symlinks in that situation."""
265+
if not PY3 and is_ironpython():
266+
if is_windows():
267+
return _realpath_ipy_win(path)
268+
else:
269+
return _realpath_ipy_posix(path)
270+
271+
return os.path.realpath(path)
272+
273+
274+
def _realpath_ipy_win(path):
275+
dirname = os.path.basename(path)
276+
parent_path = os.path.join(path, '..')
277+
278+
args = 'dir /c "{}" /Al'.format(parent_path)
279+
process = subprocess.Popen(args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
280+
281+
output, _error = process.communicate()
282+
matches = SYMLINK_REGEX.finditer(output)
283+
for match in matches:
284+
match_name = match.groups()[0].strip()
285+
match_link = match.groups()[1]
286+
287+
if match_name == dirname:
288+
return match_link
289+
290+
return ValueError('path not found: {}'.format(path))
291+
292+
293+
def _realpath_ipy_posix(path):
294+
args = 'readlink -f "{}"'.format(path)
295+
process = subprocess.Popen(args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
296+
output, _error = process.communicate()
297+
return output
298+
299+
255300
# Cache whatever symlink function works (native or polyfill)
256301
_os_symlink = None
257302

0 commit comments

Comments
 (0)