Skip to content
This repository has been archived by the owner on Jan 25, 2023. It is now read-only.

Commit

Permalink
Added configurable Python.Runtime.dll loader.
Browse files Browse the repository at this point in the history
  • Loading branch information
dmitriyse committed Jul 22, 2016
1 parent 5f21a85 commit 2ae87e7
Show file tree
Hide file tree
Showing 18 changed files with 678 additions and 2 deletions.
178 changes: 176 additions & 2 deletions pythonnet.sln

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/config/FodyWeavers.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers>
<ModuleInit/>
</Weavers>
17 changes: 17 additions & 0 deletions src/config/ModuleInitializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;

using Python.Config;

/// <summary>
/// Used by the ModuleInit. All code inside the Initialize method is ran as soon as the assembly is loaded.
/// </summary>
public static class ModuleInitializer
{
/// <summary>
/// Initializes the module.
/// </summary>
public static void Initialize()
{
PythonConfig.EnsureInitialized();
}
}
36 changes: 36 additions & 0 deletions src/config/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("config")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("config")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("c4779672-f707-4778-b901-7c63548a3d6a")]

// PythonVersion information for an assembly consists of the following four values:
//
// Major PythonVersion
// Minor PythonVersion
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
77 changes: 77 additions & 0 deletions src/config/Python.Config.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{C4779672-F707-4778-B901-7C63548A3D6A}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Python.Config</RootNamespace>
<AssemblyName>Python.Config</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Compile Include="ModuleInitializer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PythonConfig.cs" />
<Compile Include="PythonConfigSection.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Configuration" />
</ItemGroup>
<ItemGroup>
<Content Include="FodyWeavers.xml" />
<EmbeddedResource Include="Python.Runtime-Py27-Win64.dll">
<LogicalName>Python.Runtime-Py27-Win64.dll</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="Python.Runtime-Py35-Win64.dll">
<LogicalName>Python.Runtime-Py35-Win64.dll</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="Python.Runtime-Py27-Linux64.dll">
<LogicalName>Python.Runtime-Py27-Linux64.dll</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="Python.Runtime-Py35-Linux64.dll">
<LogicalName>Python.Runtime-Py35-Linux64.dll</LogicalName>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\packages\Fody.1.29.4\build\portable-net+sl+win+wpa+wp\Fody.targets" Condition="Exists('..\..\packages\Fody.1.29.4\build\portable-net+sl+win+wpa+wp\Fody.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\packages\Fody.1.29.4\build\portable-net+sl+win+wpa+wp\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Fody.1.29.4\build\portable-net+sl+win+wpa+wp\Fody.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
Binary file added src/config/Python.Runtime-Py27-Linux64.dll
Binary file not shown.
Binary file added src/config/Python.Runtime-Py27-Win64.dll
Binary file not shown.
Binary file added src/config/Python.Runtime-Py35-Linux64.dll
Binary file not shown.
Binary file added src/config/Python.Runtime-Py35-Win64.dll
Binary file not shown.
149 changes: 149 additions & 0 deletions src/config/PythonConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
namespace Python.Config
{
using System;
using System.Configuration;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;

/// <summary>
/// Python.Net runtime configuration.
/// </summary>
public static class PythonConfig
{
private static object _assemblyLoadLock = new object();

private static Exception _configException;

private static Assembly _pythonRuntimeAssembly;

private static string _pythonVersion;

private static Regex _versionNumberRegex = new Regex("^(?<major>\\d)\\.(?<minor>\\d)$");

/// <summary>
/// Initializes static members of the <see cref="PythonConfig"/> class.
/// </summary>
static PythonConfig()
{
if (IntPtr.Size == 4)
{
throw new NotSupportedException("32 bit platform not supported by Python.Config library.");
}
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolveHandler;
}

/// <summary>
/// Loaded assembly name.
/// </summary>
public static string LoadedRuntimeAssembly { get; private set; }

/// <summary>
/// Pyhon runtime version. Can be changed only
/// </summary>
public static string PythonVersion
{
get
{
if (_pythonVersion == null)
{
try
{
var pythonConfigSection = (PythonConfigSection)ConfigurationManager.GetSection("pythonConfig");
_pythonVersion = pythonConfigSection.PythonVersion;

ValidatePythonVersion(_pythonVersion);
}
catch (Exception ex)
{
_pythonVersion = "2.7";
_configException = ex;
AppDomain.CurrentDomain.SetData("PythonConfigException", _configException);
}
}

return _pythonVersion;
}

private set
{
// Disabling this feature. Dynamic version select should be implemented through callback in configuration file.
if (IsRuntimeAssemblyLoaded)
{
throw new InvalidOperationException(
"Python version can be changed only before Python.Runtime assembly loaded by CLR.");
}

ValidatePythonVersion(value);
_pythonVersion = value;
}
}

/// <summary>
/// Used internally to determine that Python.Runtime assembly already loaded.
/// </summary>
internal static bool IsRuntimeAssemblyLoaded { get; set; }

/// <summary>
/// Forces Python Config library to be initialized.
/// </summary>
public static void EnsureInitialized()
{
// Do nothing, but forces CLR to load PythonConfig type.
}

private static Assembly AssemblyResolveHandler(object sender, ResolveEventArgs args)
{
if (args.Name.StartsWith("Python.Runtime"))
{
lock (_assemblyLoadLock)
{
if (_pythonRuntimeAssembly != null)
{
return _pythonRuntimeAssembly;
}

string platform = Path.DirectorySeparatorChar == '\\' ? "Win64" : "Linux64";

// We will load assembly here.
string resourceName = $"Python.Runtime-Py{PythonVersion.Replace(".", string.Empty)}-{platform}.dll";

// looks for the assembly from the resources and load it
using (var stream = typeof(PythonConfig).Assembly.GetManifestResourceStream(resourceName))
{
if (stream != null)
{
byte[] assemblyData = new byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
try
{
_pythonRuntimeAssembly = Assembly.Load(assemblyData);
LoadedRuntimeAssembly = resourceName;
return _pythonRuntimeAssembly;
}
catch (Exception ex)
{
AppDomain.CurrentDomain.SetData("PythonConfigException", ex);
}
}
}
}
}

return null;
}

private static void ValidatePythonVersion(string version)
{
if (!_versionNumberRegex.Match(version).Success)
{
throw new ArgumentException("Python version should be specified in x.y, for example \"2.7\".");
}

if (version != "2.7" && version != "3.5")
{
throw new ArgumentException("Unsupported python version. Only 2.7 and 3.5 are supported.");
}
}
}
}
22 changes: 22 additions & 0 deletions src/config/PythonConfigSection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Python.Config
{
using System;
using System.Configuration;

public class PythonConfigSection : ConfigurationSection
{
[ConfigurationProperty("pythonVersion", DefaultValue = "2.7", IsRequired = true)]
public string PythonVersion
{
get
{
return (string)this["pythonVersion"];
}

set
{
this["pythonVersion"] = value;
}
}
}
}
5 changes: 5 additions & 0 deletions src/config/packages.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Fody" version="1.29.4" targetFramework="net40" developmentDependency="true" />
<package id="ModuleInit.Fody" version="1.5.9.0" targetFramework="net40" developmentDependency="true" />
</packages>
7 changes: 7 additions & 0 deletions src/configtest/App.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="pythonConfig" type="Python.Config.PythonConfigSection, Python.Config, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</configSections>
<pythonConfig pythonVersion="3.5"/>
</configuration>
65 changes: 65 additions & 0 deletions src/configtest/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
namespace Python.Config.Test
{
using System;

using Python.Runtime;

internal class Program
{
static Program()
{
Console.WriteLine("Starting application...");
// Required to be placed in the static constructor for Mono.
PythonConfig.EnsureInitialized();
}

[STAThread]
private static int Main(string[] args)
{
// Mono workaround required to fix AssemblyResolve + EntryPoint class bug.
// Classes that was referenced from EntryPoint class cannot use assemblies resolved through "AssemblyResolve"
Action monoWorkaround = () =>
{
try
{
if (PythonConfig.LoadedRuntimeAssembly != null)
{
Console.WriteLine(
$"Python.runtime.dll substituted by {PythonConfig.LoadedRuntimeAssembly}.");
}
else
{
Console.WriteLine(
$"Python.runtime.dll was loaded from application directory.");
}
// You should put this initialized only if some component starting to use it before first application configuration file read attempt.
// So in rare cases.
PythonEngine.Initialize();
// Like that.
try
{
using (Py.GIL())
{
dynamic sysModule = Py.Import("sys");
Console.WriteLine("Python engine version:");
Console.WriteLine(sysModule.version);
}
}
finally
{
PythonEngine.Shutdown();
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
};
monoWorkaround();

return 0;
}
}
}
Loading

0 comments on commit 2ae87e7

Please sign in to comment.