Skip to content

Platform utilities

Functions that may be useful in your packages to locate and load native shared libraries.

Platform specific helpers to manage locating native dynamic libraries

This module hosts features similar to https://github.com/rdotnet/dynamic-interop-dll/blob/master/DynamicInterop/PlatformUtility.cs

augment_path_env(added_paths, subfolder=None, to_env='PATH', prepend=False)

Build a new list of directory paths, prepending prior to an existing env var with paths.

New paths are prepended only if they do already exist.

Parameters:

Name Type Description Default
added_paths Union[str, List[str]]

paths prepended

required
subfolder str

Optional subfolder name to append to each in path prepended. Useful for 64/32 bits variations. Defaults to None.

None
to_env str

Environment variable with existing Paths to start with. Defaults to 'PATH'.

'PATH'

Returns:

Name Type Description
str str

Content (set of paths), typically for a updating/setting an environment variable

Source code in refcount/putils.py
def augment_path_env(
    added_paths: Union[str, List[str]], subfolder: str = None, to_env: str = "PATH", prepend:bool=False
) -> str:
    """Build a new list of directory paths, prepending prior to an existing env var with paths. 

    New paths are prepended only if they do already exist.

    Args:
        added_paths (Union[str,List[str]]): paths prepended
        subfolder (str, optional): Optional subfolder name to append to each in path prepended. Useful for 64/32 bits variations. Defaults to None.
        to_env (str, optional): Environment variable with existing Paths to start with. Defaults to 'PATH'.

    Returns:
        str: Content (set of paths), typically for a updating/setting an environment variable
    """
    path_sep = os.pathsep
    if isinstance(added_paths, str):
        added_paths = [added_paths]
    prior_path_env = os.environ.get(to_env)
    if prior_path_env is not None:
        prior_paths = prior_path_env.split(path_sep)
    else:
        prior_paths = []
    def _my_path_join(x, subfolder):  # avoid trailing path separator
        if subfolder is not None and subfolder != "":
            return os.path.join(x, subfolder)
        else:
            return x
    if subfolder is not None:
        added_paths = [_my_path_join(x, subfolder) for x in added_paths]
    added_paths = [x for x in added_paths if os.path.exists(x)]
    if prepend:
        new_paths = added_paths + prior_paths
    else:
        new_paths = prior_paths + added_paths
    # TODO: check for duplicate folders, perhaps.
    new_env_val = path_sep.join(new_paths)
    return new_env_val

build_new_path_env(from_env='LIBRARY_PATH', to_env='PATH', platform=None)

Propose an update to an existing environment variable, based on the path(s) specified in another environment variable. This function is effectively meant to be useful on Windows only.

Parameters:

Name Type Description Default
from_env str

name of the source environment variable specifying the location(s) of custom libraries to load. Defaults to 'LIBRARY_PATH'.

'LIBRARY_PATH'
to_env str

environment variable to update, most likely the Windows PATH env var. Defaults to 'PATH'.

'PATH'

Returns:

Name Type Description
str str

the proposed updated content for the 'to_env' environment variable.

Source code in refcount/putils.py
def build_new_path_env (from_env:str='LIBRARY_PATH', to_env:str='PATH', platform:str=None) -> str:
    """Propose an update to an existing environment variable, based on the path(s) specified in another environment variable. This function is effectively meant to be useful on Windows only.

    Args:
        from_env (str, optional): name of the source environment variable specifying the location(s) of custom libraries to load. Defaults to 'LIBRARY_PATH'.
        to_env (str, optional): environment variable to update, most likely the Windows PATH env var. Defaults to 'PATH'.

    Returns:
        str: the proposed updated content for the 'to_env' environment variable. 
    """
    platform = sys.platform if platform is None else platform
    path_sep = os.pathsep
    shared_lib_paths = os.environ.get(from_env)
    if shared_lib_paths is not None:
        # We could consider a call to a logger info here
        subfolder = _win_architecture()
        shared_lib_paths_vec = shared_lib_paths.split(path_sep)
        return augment_path_env(shared_lib_paths_vec, subfolder, to_env=to_env)
    else:
        print("WARNING: a function was called to look for environment variable '{0}' to update the environment variable '{1}', but was not found. This may be fine, but if the package fails to load because '{2}' is not found, this is a likely cause.".format(from_env, to_env, lib_short_fname))
        return ""

find_full_path(name, prefix=None)

Find the full path of a library in under the python installation directory, or as devised by ctypes.find_library

Parameters:

Name Type Description Default
name str

Library name, e.g. 'R' for the R programming language.

required

Returns:

Type Description
Union[str, None]

Union[str, None]: First suitable library full file name.

Examples:

>>> from refcount.putils import *
>>> find_full_path('gfortran')
'/home/xxxyyy/anaconda3/envs/wqml/lib/libgfortran.so'
>>> find_full_path('R')
'libR.so'
Source code in refcount/putils.py
def find_full_path(name: str, prefix:Optional[str]=None) -> Union[str, None]:
    """Find the full path of a library in under the python 
        installation directory, or as devised by ctypes.find_library

    Args:
        name (str): Library name, e.g. 'R' for the R programming language.

    Returns:
        Union[str, None]: First suitable library full file name.

    Examples:
        >>> from refcount.putils import *
        >>> find_full_path('gfortran')
        '/home/xxxyyy/anaconda3/envs/wqml/lib/libgfortran.so'
        >>> find_full_path('R')
        'libR.so'
    """    
    full_libpath = None
    if prefix is None:
        prefix = sys.prefix
    if name is None:
        return None
    else:
        lib_short_fname = library_short_filename(name)
        prefixed_lib_pat = os.path.join(prefix, "lib*", lib_short_fname)
        prefixed_libs = glob(prefixed_lib_pat)
        if prefixed_libs:
            full_libpath = prefixed_libs[0]
    if not full_libpath:
        full_libpath = ctypes_find_library(name)
    return full_libpath

library_short_filename(library_name, platform=None)

Based on the library name, return the platform-specific expected library short file name

Parameters:

Name Type Description Default
library_name str

name of the library, for instance 'R', which results out of this function as 'libR.so' on Linux and 'R.dll' on Windows

required

Raises:

Type Description
ValueError

invalid argument

Returns:

Name Type Description
str str

expected short file name for the library, for this platform

Source code in refcount/putils.py
def library_short_filename(library_name: str, platform:Optional[str] = None) -> str:
    """Based on the library name, return the platform-specific expected library short file name

    Args:
        library_name (str): name of the library, for instance 'R', which results out of this 
            function  as 'libR.so' on Linux and 'R.dll' on Windows

    Raises:
        ValueError: invalid argument

    Returns:
        str: expected short file name for the library, for this platform
    """
    if platform is None:
        platform = sys.platform
    if library_name is None:
        raise ValueError("library_name cannot be None")
    else:
        if platform == "win32":
            return "{}.dll".format(library_name)
        elif platform == "linux":
            return "lib{}.so".format(library_name)
        elif platform == "darwin":
            return "lib{}.dylib".format(library_name)
        else:
            raise NotImplementedError(f"Platform '{platform}' is not (yet) supported")

update_path_windows(from_env='LIBRARY_PATH', to_env='PATH')

If called on Windows, append an environment variable, based on the path(s) specified in another environment variable. This function is effectively meant to be useful on Windows only.

Parameters:

Name Type Description Default
from_env str

name of the source environment variable specifying the location(s) of custom libraries to load. Defaults to 'LIBRARY_PATH'.

'LIBRARY_PATH'
to_env str

environment variable to update, most likely the Windows PATH env var. Defaults to 'PATH'.

'PATH'

Returns:

Type Description
None

None

Source code in refcount/putils.py
def update_path_windows (from_env:str='LIBRARY_PATH', to_env:str='PATH') -> None:
    """If called on Windows, append an environment variable, based on the path(s) specified in another environment variable. This function is effectively meant to be useful on Windows only.

    Args:
        from_env (str, optional): name of the source environment variable specifying the location(s) of custom libraries to load. Defaults to 'LIBRARY_PATH'.
        to_env (str, optional): environment variable to update, most likely the Windows PATH env var. Defaults to 'PATH'.

    Returns:
        None
    """
    if(sys.platform == 'win32'):
        os.environ[to_env] = build_new_path_env(from_env, to_env, sys.platform)