зеркало из https://github.com/mono/ikvm-fork.git
When building for .NET 4.0 we can derived ModuleBuilder from Module and AssemblyBuilder from Assembly.
This commit is contained in:
Родитель
a9751b487e
Коммит
7d0d8be096
|
@ -2442,7 +2442,7 @@ namespace IKVM.Internal
|
||||||
}
|
}
|
||||||
if (compiler.options.sharedclassloader[0] != compiler)
|
if (compiler.options.sharedclassloader[0] != compiler)
|
||||||
{
|
{
|
||||||
compiler.GetTypeWrapperFactory().ModuleBuilder.Assembly.SetCustomAttribute(mainAssembly);
|
((AssemblyBuilder)compiler.GetTypeWrapperFactory().ModuleBuilder.Assembly).SetCustomAttribute(mainAssembly);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int rc = compiler.Compile();
|
int rc = compiler.Compile();
|
||||||
|
|
|
@ -33,7 +33,12 @@ using System.Security;
|
||||||
|
|
||||||
namespace IKVM.Reflection.Emit
|
namespace IKVM.Reflection.Emit
|
||||||
{
|
{
|
||||||
public sealed class AssemblyBuilder : IkvmAssembly
|
public sealed class AssemblyBuilder :
|
||||||
|
#if NET_4_0
|
||||||
|
Assembly
|
||||||
|
#else
|
||||||
|
IkvmAssembly
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
private readonly AssemblyName name;
|
private readonly AssemblyName name;
|
||||||
private readonly string dir;
|
private readonly string dir;
|
||||||
|
@ -71,15 +76,16 @@ namespace IKVM.Reflection.Emit
|
||||||
|
|
||||||
public static AssemblyBuilder DefineDynamicAssembly(AssemblyName name, AssemblyBuilderAccess access, string dir)
|
public static AssemblyBuilder DefineDynamicAssembly(AssemblyName name, AssemblyBuilderAccess access, string dir)
|
||||||
{
|
{
|
||||||
return DefineDynamicAssembly(name, access, dir, null, null, null);
|
return new AssemblyBuilder(name, dir, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete]
|
||||||
public static AssemblyBuilder DefineDynamicAssembly(AssemblyName name, AssemblyBuilderAccess access, string dir, PermissionSet requiredPermissions, PermissionSet optionalPermissions, PermissionSet refusedPermissions)
|
public static AssemblyBuilder DefineDynamicAssembly(AssemblyName name, AssemblyBuilderAccess access, string dir, PermissionSet requiredPermissions, PermissionSet optionalPermissions, PermissionSet refusedPermissions)
|
||||||
{
|
{
|
||||||
return new AssemblyBuilder(name, dir, requiredPermissions, optionalPermissions, refusedPermissions);
|
return new AssemblyBuilder(name, dir, requiredPermissions, optionalPermissions, refusedPermissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AssemblyName GetName()
|
public override AssemblyName GetName()
|
||||||
{
|
{
|
||||||
AssemblyName n = new AssemblyName();
|
AssemblyName n = new AssemblyName();
|
||||||
n.Name = name.Name;
|
n.Name = name.Name;
|
||||||
|
@ -96,7 +102,7 @@ namespace IKVM.Reflection.Emit
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string FullName
|
public override string FullName
|
||||||
{
|
{
|
||||||
get { return GetName().FullName; }
|
get { return GetName().FullName; }
|
||||||
}
|
}
|
||||||
|
@ -175,17 +181,23 @@ namespace IKVM.Reflection.Emit
|
||||||
}
|
}
|
||||||
int token = 0x20000000 + manifestModule.Tables.Assembly.AddRecord(assemblyRecord);
|
int token = 0x20000000 + manifestModule.Tables.Assembly.AddRecord(assemblyRecord);
|
||||||
|
|
||||||
|
#pragma warning disable 618
|
||||||
|
// this values are obsolete, but we already know that so we disable the warning
|
||||||
|
System.Security.Permissions.SecurityAction requestMinimum = System.Security.Permissions.SecurityAction.RequestMinimum;
|
||||||
|
System.Security.Permissions.SecurityAction requestOptional = System.Security.Permissions.SecurityAction.RequestOptional;
|
||||||
|
System.Security.Permissions.SecurityAction requestRefuse = System.Security.Permissions.SecurityAction.RequestRefuse;
|
||||||
|
#pragma warning restore 618
|
||||||
if (requiredPermissions != null)
|
if (requiredPermissions != null)
|
||||||
{
|
{
|
||||||
manifestModule.AddDeclaritiveSecurity(token, System.Security.Permissions.SecurityAction.RequestMinimum, requiredPermissions);
|
manifestModule.AddDeclaritiveSecurity(token, requestMinimum, requiredPermissions);
|
||||||
}
|
}
|
||||||
if (optionalPermissions != null)
|
if (optionalPermissions != null)
|
||||||
{
|
{
|
||||||
manifestModule.AddDeclaritiveSecurity(token, System.Security.Permissions.SecurityAction.RequestOptional, optionalPermissions);
|
manifestModule.AddDeclaritiveSecurity(token, requestOptional, optionalPermissions);
|
||||||
}
|
}
|
||||||
if (refusedPermissions != null)
|
if (refusedPermissions != null)
|
||||||
{
|
{
|
||||||
manifestModule.AddDeclaritiveSecurity(token, System.Security.Permissions.SecurityAction.RequestRefuse, refusedPermissions);
|
manifestModule.AddDeclaritiveSecurity(token, requestRefuse, refusedPermissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteBuffer versionInfoData = null;
|
ByteBuffer versionInfoData = null;
|
||||||
|
@ -303,7 +315,7 @@ namespace IKVM.Reflection.Emit
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ImageRuntimeVersion
|
public override string ImageRuntimeVersion
|
||||||
{
|
{
|
||||||
get { return imageRuntimeVersion; }
|
get { return imageRuntimeVersion; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,6 +125,13 @@ namespace IKVM.Reflection.Emit
|
||||||
get { return methodBuilder.MetadataToken; }
|
get { return methodBuilder.MetadataToken; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if NET_4_0
|
||||||
|
public override Module Module
|
||||||
|
{
|
||||||
|
get { return methodBuilder.Module; }
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
internal ModuleBuilder ModuleBuilder
|
internal ModuleBuilder ModuleBuilder
|
||||||
{
|
{
|
||||||
get { return methodBuilder.ModuleBuilder; }
|
get { return methodBuilder.ModuleBuilder; }
|
||||||
|
|
|
@ -213,6 +213,12 @@ namespace IKVM.Reflection.Emit
|
||||||
return MethodBuilder.Copy(requiredCustomModifiers);
|
return MethodBuilder.Copy(requiredCustomModifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if NET_4_0
|
||||||
|
public override Module Module
|
||||||
|
{
|
||||||
|
get { return typeBuilder.Module; }
|
||||||
|
}
|
||||||
|
#endif
|
||||||
internal void WriteFieldRecords(MetadataWriter mw)
|
internal void WriteFieldRecords(MetadataWriter mw)
|
||||||
{
|
{
|
||||||
mw.Write((short)attribs);
|
mw.Write((short)attribs);
|
||||||
|
|
|
@ -28,6 +28,15 @@ using IKVM.Reflection.Emit.Impl;
|
||||||
|
|
||||||
namespace IKVM.Reflection.Emit
|
namespace IKVM.Reflection.Emit
|
||||||
{
|
{
|
||||||
|
#if NET_4_0
|
||||||
|
public static class IkvmAssembly
|
||||||
|
{
|
||||||
|
public static Assembly GetAssembly(Type type)
|
||||||
|
{
|
||||||
|
return type.Assembly;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
public abstract class IkvmAssembly
|
public abstract class IkvmAssembly
|
||||||
{
|
{
|
||||||
private static readonly Dictionary<Assembly, IkvmAssembly> assemblies = new Dictionary<Assembly, IkvmAssembly>();
|
private static readonly Dictionary<Assembly, IkvmAssembly> assemblies = new Dictionary<Assembly, IkvmAssembly>();
|
||||||
|
@ -47,6 +56,21 @@ namespace IKVM.Reflection.Emit
|
||||||
{
|
{
|
||||||
return asm.GetType(typeName);
|
return asm.GetType(typeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override string FullName
|
||||||
|
{
|
||||||
|
get { return asm.FullName; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override AssemblyName GetName()
|
||||||
|
{
|
||||||
|
return asm.GetName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ImageRuntimeVersion
|
||||||
|
{
|
||||||
|
get { return asm.ImageRuntimeVersion; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IkvmAssembly GetAssembly(Type type)
|
public static IkvmAssembly GetAssembly(Type type)
|
||||||
|
@ -66,5 +90,9 @@ namespace IKVM.Reflection.Emit
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Type GetType(string typeName);
|
public abstract Type GetType(string typeName);
|
||||||
|
public abstract string FullName { get; }
|
||||||
|
public abstract AssemblyName GetName();
|
||||||
|
public abstract string ImageRuntimeVersion { get; }
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,10 +28,17 @@ namespace IKVM.Reflection.Emit.Impl
|
||||||
{
|
{
|
||||||
public abstract class TypeBase : Type
|
public abstract class TypeBase : Type
|
||||||
{
|
{
|
||||||
|
#if NET_4_0
|
||||||
|
public abstract override Assembly Assembly
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
#else
|
||||||
public sealed override Assembly Assembly
|
public sealed override Assembly Assembly
|
||||||
{
|
{
|
||||||
get { throw new NotSupportedException(); }
|
get { throw new NotSupportedException(); }
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
public abstract override string AssemblyQualifiedName
|
public abstract override string AssemblyQualifiedName
|
||||||
{
|
{
|
||||||
|
@ -158,10 +165,17 @@ namespace IKVM.Reflection.Emit.Impl
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if NET_4_0
|
||||||
|
public abstract override Module Module
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
#else
|
||||||
public sealed override Module Module
|
public sealed override Module Module
|
||||||
{
|
{
|
||||||
get { throw new NotSupportedException(); }
|
get { throw new NotSupportedException(); }
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
public override Type UnderlyingSystemType
|
public override Type UnderlyingSystemType
|
||||||
{
|
{
|
||||||
|
|
|
@ -434,6 +434,13 @@ namespace IKVM.Reflection.Emit
|
||||||
get { return pseudoToken; }
|
get { return pseudoToken; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if NET_4_0
|
||||||
|
public override Module Module
|
||||||
|
{
|
||||||
|
get { return typeBuilder.Module; }
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
internal void Bake()
|
internal void Bake()
|
||||||
{
|
{
|
||||||
if (ilgen != null)
|
if (ilgen != null)
|
||||||
|
|
|
@ -34,7 +34,35 @@ using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace IKVM.Reflection.Emit
|
namespace IKVM.Reflection.Emit
|
||||||
{
|
{
|
||||||
public sealed class ModuleBuilder : ITypeOwner
|
#if !NET_4_0
|
||||||
|
public abstract class IkvmModule
|
||||||
|
{
|
||||||
|
public abstract Type GetType(string className);
|
||||||
|
public abstract Type GetType(string className, bool throwOnError, bool ignoreCase);
|
||||||
|
public abstract string FullyQualifiedName { get; }
|
||||||
|
public abstract Guid ModuleVersionId { get; }
|
||||||
|
public abstract Type ResolveType(int metadataToken, Type[] genericTypeArguments, Type[] genericMethodArguments);
|
||||||
|
public abstract MethodBase ResolveMethod(int metadataToken, Type[] genericTypeArguments, Type[] genericMethodArguments);
|
||||||
|
|
||||||
|
public Type ResolveType(int metadataToken)
|
||||||
|
{
|
||||||
|
return ResolveType(metadataToken, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodBase ResolveMethod(int metadataToken)
|
||||||
|
{
|
||||||
|
return ResolveMethod(metadataToken, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public sealed class ModuleBuilder :
|
||||||
|
#if NET_4_0
|
||||||
|
Module,
|
||||||
|
#else
|
||||||
|
IkvmModule,
|
||||||
|
#endif
|
||||||
|
ITypeOwner
|
||||||
{
|
{
|
||||||
private readonly Guid mvid = Guid.NewGuid();
|
private readonly Guid mvid = Guid.NewGuid();
|
||||||
private readonly AssemblyBuilder asm;
|
private readonly AssemblyBuilder asm;
|
||||||
|
@ -364,17 +392,24 @@ namespace IKVM.Reflection.Emit
|
||||||
manifestResources.Position = savePosition;
|
manifestResources.Position = savePosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AssemblyBuilder Assembly
|
#if NET_4_0
|
||||||
|
public override Assembly Assembly
|
||||||
{
|
{
|
||||||
get { return asm; }
|
get { return asm; }
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
public IkvmAssembly Assembly
|
||||||
|
{
|
||||||
|
get { return asm; }
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
public Type GetType(string className)
|
public override Type GetType(string className)
|
||||||
{
|
{
|
||||||
return GetType(className, false, false);
|
return GetType(className, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Type GetType(string className, bool throwOnError, bool ignoreCase)
|
public override Type GetType(string className, bool throwOnError, bool ignoreCase)
|
||||||
{
|
{
|
||||||
if (ignoreCase)
|
if (ignoreCase)
|
||||||
{
|
{
|
||||||
|
@ -951,13 +986,26 @@ namespace IKVM.Reflection.Emit
|
||||||
get { return this; }
|
get { return this; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public Type ResolveType(int metadataToken)
|
public new Type ResolveType(int metadataToken)
|
||||||
{
|
{
|
||||||
return types[(metadataToken & 0xFFFFFF) - 1];
|
return types[(metadataToken & 0xFFFFFF) - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
public MethodBase ResolveMethod(int metadataToken)
|
public override Type ResolveType(int metadataToken, Type[] genericTypeArguments, Type[] genericMethodArguments)
|
||||||
{
|
{
|
||||||
|
if (genericTypeArguments != null || genericMethodArguments != null)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
return types[(metadataToken & 0xFFFFFF) - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public override MethodBase ResolveMethod(int metadataToken, Type[] genericTypeArguments, Type[] genericMethodArguments)
|
||||||
|
{
|
||||||
|
if (genericTypeArguments != null || genericMethodArguments != null)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
// HACK if we're given a SymbolToken, we need to convert back
|
// HACK if we're given a SymbolToken, we need to convert back
|
||||||
if ((metadataToken & 0xFF000000) == 0x06000000)
|
if ((metadataToken & 0xFF000000) == 0x06000000)
|
||||||
{
|
{
|
||||||
|
@ -975,7 +1023,7 @@ namespace IKVM.Reflection.Emit
|
||||||
return ((TypeBuilder)moduleType).LookupMethod(metadataToken);
|
return ((TypeBuilder)moduleType).LookupMethod(metadataToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string FullyQualifiedName
|
public override string FullyQualifiedName
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
@ -983,7 +1031,7 @@ namespace IKVM.Reflection.Emit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Guid ModuleVersionId
|
public override Guid ModuleVersionId
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
|
@ -126,6 +126,18 @@ namespace IKVM.Reflection.Emit
|
||||||
{
|
{
|
||||||
get { return method; }
|
get { return method; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if NET_4_0
|
||||||
|
public override Assembly Assembly
|
||||||
|
{
|
||||||
|
get { return moduleBuilder.Assembly; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Module Module
|
||||||
|
{
|
||||||
|
get { return moduleBuilder; }
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class TypeBuilder : Impl.TypeBase, ITypeOwner
|
public sealed class TypeBuilder : Impl.TypeBase, ITypeOwner
|
||||||
|
@ -675,6 +687,18 @@ namespace IKVM.Reflection.Emit
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if NET_4_0
|
||||||
|
public override Assembly Assembly
|
||||||
|
{
|
||||||
|
get { return owner.ModuleBuilder.Assembly; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Module Module
|
||||||
|
{
|
||||||
|
get { return owner.ModuleBuilder; }
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class ArrayType : Impl.TypeBase
|
sealed class ArrayType : Impl.TypeBase
|
||||||
|
@ -769,6 +793,18 @@ namespace IKVM.Reflection.Emit
|
||||||
}
|
}
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if NET_4_0
|
||||||
|
public override Assembly Assembly
|
||||||
|
{
|
||||||
|
get { return type.Assembly; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Module Module
|
||||||
|
{
|
||||||
|
get { return type.Module; }
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class BakedType : Impl.TypeBase
|
sealed class BakedType : Impl.TypeBase
|
||||||
|
@ -908,6 +944,18 @@ namespace IKVM.Reflection.Emit
|
||||||
{
|
{
|
||||||
get { return typeBuilder.ModuleBuilder; }
|
get { return typeBuilder.ModuleBuilder; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if NET_4_0
|
||||||
|
public override Assembly Assembly
|
||||||
|
{
|
||||||
|
get { return typeBuilder.Assembly; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Module Module
|
||||||
|
{
|
||||||
|
get { return typeBuilder.Module; }
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class GenericType : Impl.TypeBase
|
sealed class GenericType : Impl.TypeBase
|
||||||
|
@ -1040,6 +1088,18 @@ namespace IKVM.Reflection.Emit
|
||||||
{
|
{
|
||||||
get { return typeBuilder.ModuleBuilder; }
|
get { return typeBuilder.ModuleBuilder; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if NET_4_0
|
||||||
|
public override Assembly Assembly
|
||||||
|
{
|
||||||
|
get { return typeBuilder.Assembly; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Module Module
|
||||||
|
{
|
||||||
|
get { return typeBuilder.Module; }
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class MonoHackGenericType : Impl.TypeBase
|
public sealed class MonoHackGenericType : Impl.TypeBase
|
||||||
|
@ -1173,5 +1233,17 @@ namespace IKVM.Reflection.Emit
|
||||||
{
|
{
|
||||||
get { return null; }
|
get { return null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if NET_4_0
|
||||||
|
public override Assembly Assembly
|
||||||
|
{
|
||||||
|
get { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Module Module
|
||||||
|
{
|
||||||
|
get { return null; }
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,14 +35,14 @@ namespace IKVM.Internal
|
||||||
{
|
{
|
||||||
internal static bool IsSameAssembly(Type type1, Type type2)
|
internal static bool IsSameAssembly(Type type1, Type type2)
|
||||||
{
|
{
|
||||||
#if IKVM_REF_EMIT
|
#if IKVM_REF_EMIT && !NET_4_0
|
||||||
return IkvmAssembly.GetAssembly(type1) == IkvmAssembly.GetAssembly(type2);
|
return IkvmAssembly.GetAssembly(type1) == IkvmAssembly.GetAssembly(type2);
|
||||||
#else
|
#else
|
||||||
return type1.Assembly.Equals(type2.Assembly);
|
return type1.Assembly.Equals(type2.Assembly);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if IKVM_REF_EMIT
|
#if IKVM_REF_EMIT && !NET_4_0
|
||||||
internal static bool IsFromAssembly(Type type, IkvmAssembly assembly)
|
internal static bool IsFromAssembly(Type type, IkvmAssembly assembly)
|
||||||
{
|
{
|
||||||
return IkvmAssembly.GetAssembly(type) == assembly;
|
return IkvmAssembly.GetAssembly(type) == assembly;
|
||||||
|
@ -54,7 +54,7 @@ namespace IKVM.Internal
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if IKVM_REF_EMIT
|
#if IKVM_REF_EMIT && !NET_4_0
|
||||||
internal static IkvmAssembly GetAssembly(Type type)
|
internal static IkvmAssembly GetAssembly(Type type)
|
||||||
{
|
{
|
||||||
return IkvmAssembly.GetAssembly(type);
|
return IkvmAssembly.GetAssembly(type);
|
||||||
|
|
Загрузка…
Ссылка в новой задаче