# SPDX-License-Identifier: Apache-2.0
# Copyright Contributors to the Rez Project
import errno
import os
import os.path
import re
from rez.utils.filesystem import find_matching_symlink
[docs]def get_next_base26(prev=None):
"""Increment letter-based IDs.
Generates IDs like ['a', 'b', ..., 'z', 'aa', ab', ..., 'az', 'ba', ...]
Returns:
str: Next base-26 ID.
"""
if not prev:
return 'a'
r = re.compile("^[a-z]*$")
if not r.match(prev):
raise ValueError("Invalid base26")
if not prev.endswith('z'):
return prev[:-1] + chr(ord(prev[-1]) + 1)
return get_next_base26(prev[:-1]) + 'a'
[docs]def create_unique_base26_symlink(path, source):
"""Create a base-26 symlink in `path` pointing to `source`.
If such a symlink already exists, it is returned. Note that there is a small
chance that this function may create a new symlink when there is already one
pointed at `source`.
Assumes `path` only contains base26 symlinks.
Returns:
str: Path to created symlink.
"""
retries = 0
while True:
# if a link already exists that points at `source`, return it
name = find_matching_symlink(path, source)
if name:
return os.path.join(path, name)
# get highest current symlink in path
names = [
x for x in os.listdir(path)
if os.path.islink(os.path.join(path, x))
]
if names:
prev = max(names)
else:
prev = None
linkname = get_next_base26(prev)
linkpath = os.path.join(path, linkname)
# attempt to create the symlink
try:
os.symlink(source, linkpath)
return linkpath
except OSError as e:
if e.errno != errno.EEXIST:
raise
# if we're here, the same named symlink was created in parallel
# somewhere. Try again up to N times.
#
if retries > 10:
raise RuntimeError(
"Variant shortlink not created - there was too much contention.")
retries += 1