[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/zilo555/pythonnet/master/src/runtime/Util/PythonEnvironment.cs [Back]  [Original]

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using static System.FormattableString;

namespace Python.Runtime;


internal class PythonEnvironment
{
    readonly static string PYDLL_ENV_VAR = "PYTHONNET_PYDLL";
    readonly static string PYEXE_ENV_VAR = "PYTHONNET_PYEXE";
    readonly static string PYNET_VENV_ENV_VAR = "PYTHONNET_VENV";
    readonly static string VENV_ENV_VAR = "VIRTUAL_ENV";

    public string? VenvPath { get; private set; }
    public string? Home { get; private set; }
    public Version? Version { get; private set; }
    public string? ProgramName { get; set; }
    public string? LibPython { get; set; }

    public bool IsValid =>
        !string.IsNullOrEmpty(ProgramName) && !string.IsNullOrEmpty(LibPython);


    // TODO: Move the lib-guessing step to separate function, use together with
    // PYTHONNET_PYEXE or a path lookup as last resort

    // Initialize PythonEnvironment instance from environment variables.
    //
    // If PYTHONNET_PYEXE and PYTHONNET_PYDLL are set, these always have precedence.
    // If PYTHONNET_VENV or VIRTUAL_ENV is set, we interpret the environment as a venv
    // and set the ProgramName/LibPython accordingly. PYTHONNET_VENV takes precedence.
    public static PythonEnvironment FromEnv()
    {
        var pydll = Environment.GetEnvironmentVariable(PYDLL_ENV_VAR);
        var pydllSet = !string.IsNullOrEmpty(pydll);
        var pyexe = Environment.GetEnvironmentVariable(PYEXE_ENV_VAR);
        var pyexeSet = !string.IsNullOrEmpty(pyexe);
        var pynetVenv = Environment.GetEnvironmentVariable(PYNET_VENV_ENV_VAR);
        var pynetVenvSet = !string.IsNullOrEmpty(pynetVenv);
        var venv = Environment.GetEnvironmentVariable(VENV_ENV_VAR);
        var venvSet = !string.IsNullOrEmpty(venv);

        PythonEnvironment? res = new();

        if (pynetVenvSet)
            res = FromVenv(pynetVenv) ?? res;
        else if (venvSet)
            res = FromVenv(venv) ?? res;

        if (pyexeSet)
            res.ProgramName = pyexe;

        if (pydllSet)
            res.LibPython = pydll;

        return res;
    }

    public static PythonEnvironment? FromVenv(string path)
    {
        var env = new PythonEnvironment
        {
            VenvPath = path
        };

        string venvCfg = Path.Combine(path, "pyvenv.cfg");

        if (!File.Exists(venvCfg))
            return null;

        var settings = TryParse(venvCfg);

        if (!settings.ContainsKey("home"))
            return null;

        env.Home = settings["home"];
        var pname = ProgramNameFromPath(path);
        if (File.Exists(pname))
            env.ProgramName = pname;

        if (settings.TryGetValue("version", out string versionStr))
        {
            _ = Version.TryParse(versionStr, out Version versionObj);
            env.Version = versionObj;
        }
        else if (settings.TryGetValue("version_info", out versionStr))
        {
            _ = Version.TryParse(versionStr, out Version versionObj);
            env.Version = versionObj;
        }

        env.LibPython = FindLibPython(env.Home, env.Version);

        return env;
    }

    private static Dictionary TryParse(string venvCfg)
    {
        var settings = new Dictionary();

        string[] lines = File.ReadAllLines(venvCfg);

        // The actually used format is really primitive: " = "
        foreach (string line in lines)
        {
            var split = line.Split(new[] { '=' }, 2);

            if (split.Length != 2)
                continue;

            settings[split[0].Trim()] = split[1].Trim();
        }

        return settings;
    }

    private static string? FindLibPython(string home, Version? maybeVersion)
    {
        // TODO: Check whether there is a .dll/.so/.dylib next to the executable

        if (maybeVersion is Version version)
        {
            return FindLibPythonInHome(home, version);
        }

        return null;
    }

    private static string? FindLibPythonInHome(string home, Version version)
    {
        // Probe both  pyvenv.cfg's version field doesn't distinguish free-threaded.
        var libPythonNames = new[]
        {
            GetDefaultDllName(version),
            GetDefaultDllName(version, freeThreaded: true),
        };

        List pathsToCheck = new();
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
        {
            var arch = RuntimeInformation.ProcessArchitecture;
            if (arch == Architecture.X64 || arch == Architecture.Arm64)
            {
                // multilib systems
                pathsToCheck.Add("../lib64");
            }
            pathsToCheck.Add("../lib");
        }
        else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
        {
            pathsToCheck.Add(".");
        }
        else
        {
            pathsToCheck.Add("../lib");
        }

        return pathsToCheck
            .SelectMany(path => libPythonNames.Select(name => Path.Combine(home, path, name)))
            .FirstOrDefault(File.Exists);
    }

    private static string ProgramNameFromPath(string path)
    {
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
        {
            return Path.Combine(path, "Scripts", "python.exe");
        }
        else
        {
            return Path.Combine(path, "bin", "python");
        }
    }

    internal static string GetDefaultDllName(Version version, bool freeThreaded = false)
    {
        string prefix = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "" : "lib";

        string ftSuffix = freeThreaded ? "t" : "";

        string suffix = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
            ? Invariant($"{version.Major}{version.Minor}{ftSuffix}")
            : Invariant($"{version.Major}.{version.Minor}{ftSuffix}");

        string ext = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".dll"
            : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? ".dylib"
            : ".so";

        return prefix + "python" + suffix + ext;
    }
}

Web Proxy Viewer  |  New URL  |  Original Page