IronLanguages / ironpython3

Implementation of Python 3.x for .NET Framework that is built on top of the Dynamic Language Runtime.
Apache License 2.0
2.51k stars 290 forks source link

How to support IronPython 2.7 and 3.4 in one app? #1798

Closed andyste1 closed 6 months ago

andyste1 commented 6 months ago

We have a large desktop application that uses v2.7. It's a scientific data processing app that allows users to write their own .py scripts (as well as many "system" scripts) which the app can execute and interact with, and is in use at many customer sites.

We'd like to start using v3.4 but it's vital that the app can still run all those v2.7 scripts that are out there at customer sites. I'll therefore need to reference both versions of IronPython, and (somehow) use either the 2.7 or 3.4 script engine etc, depending on which version of the script it is. The main issue I see is that the assembly DLLs have the same name in both versions (and I'm guessing the types will also have the same names), so does anyone have a suggestion as to how I can reference/use both versions of IronPython in the same app? Releasing a new version of our app that only support v3.4, and leaving existing customers on the older version with 2.7 isn't an option.

slozier commented 6 months ago

I store the IronPython specific assemblies in separate (IronPython2/IronPython3) folders. I have an assembly resolver which is used to load the appropriate assemblies from their respective folders. Then, using a common DLR I create engines for each. Something like this:

public sealed class PythonEngine
{
    static PythonEngine()
    {
        AppDomain.CurrentDomain.AssemblyResolve += IronPythonResolver;
    }

    private static readonly Version Ipy2Version = new Version(2, 7, 12, 0);
    private static readonly Version Ipy3Version = new Version(3, 4, 1, 0);

    private static LanguageSetup GetLanguageSetup(int version)
    {
        if (version == 3)
        {
            return new LanguageSetup(
                $"IronPython.Runtime.PythonContext, IronPython, Version={Ipy3Version}, Culture=neutral, PublicKeyToken=7f709c5b713576e1",
                "IronPython 3",
                new[] { "IronPython3" },
                new[] { ".py" }
            );
        }
        else
        {
            return new LanguageSetup(
                $"IronPython.Runtime.PythonContext, IronPython, Version={Ipy2Version}, Culture=neutral, PublicKeyToken=7f709c5b713576e1",
                "IronPython 2",
                new[] { "IronPython2" },
                new[] { ".py" }
            );
        }
    }

    public static ScriptEngine Create(int version, IDictionary<string, object?>? options = null)
    {
        ScriptRuntimeSetup setup = new ScriptRuntimeSetup();

        var languageSetup = GetLanguageSetup(version);

        if (options != null)
        {
            foreach (var entry in options)
            {
                setup.Options.Add(entry.Key, entry.Value);
            }
        }

        setup.LanguageSetups.Add(languageSetup);
        var runtime = new ScriptRuntime(setup);
        return runtime.GetEngine(languageSetup.Names[0]);
    }

    private static Assembly? IronPythonResolver(object? sender, ResolveEventArgs args)
    {
        if (args.Name is null) return null;
        var assemblyName = new AssemblyName(args.Name);

        if (assemblyName.Name != null && assemblyName.Version != null && (assemblyName.Name.StartsWith("Microsoft.Scripting", StringComparison.Ordinal) || assemblyName.Name == "Microsoft.Dynamic"))
        {
            var path = Path.Combine(Application.ProgramPath, assemblyName.Name + ".dll");
            return File.Exists(path) ? Assembly.LoadFile(path) : null;
        }

        if (assemblyName.Name != null && assemblyName.Version != null && assemblyName.Name.StartsWith("IronPython", StringComparison.Ordinal))
        {
            var path = Path.Combine(Application.ProgramPath, assemblyName.Version.Major == 3 ? "IronPython3" : "IronPython2", assemblyName.Name + ".dll");
            return File.Exists(path) ? Assembly.LoadFile(path) : null;
        }

        return null;
    }
}