Bringing DependencyModel and InternalAbstractions from CLI into core-setup. Renamed InternalAbstractions to PlatformAbstractions.
This commit is contained in:
Родитель
038057a871
Коммит
e416af6140
|
@ -4,7 +4,7 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.DotNet.InternalAbstractions
|
||||
namespace Microsoft.DotNet.PlatformAbstractions
|
||||
{
|
||||
public static class ApplicationEnvironment
|
||||
{
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.DotNet.InternalAbstractions
|
||||
namespace Microsoft.DotNet.PlatformAbstractions
|
||||
{
|
||||
public struct HashCodeCombiner
|
||||
{
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Extensions.EnvironmentAbstractions
|
||||
{
|
||||
internal interface ITemporaryDirectory : IDisposable
|
||||
{
|
||||
string DirectoryPath { get; }
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.DotNet.InternalAbstractions.Native
|
||||
namespace Microsoft.DotNet.PlatformAbstractions.Native
|
||||
{
|
||||
internal static partial class NativeMethods
|
||||
{
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.DotNet.InternalAbstractions.Native
|
||||
namespace Microsoft.DotNet.PlatformAbstractions.Native
|
||||
{
|
||||
internal static partial class NativeMethods
|
||||
{
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.DotNet.InternalAbstractions.Native
|
||||
namespace Microsoft.DotNet.PlatformAbstractions.Native
|
||||
{
|
||||
internal static partial class NativeMethods
|
||||
{
|
||||
|
|
|
@ -5,7 +5,7 @@ using System;
|
|||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.DotNet.InternalAbstractions.Native
|
||||
namespace Microsoft.DotNet.PlatformAbstractions.Native
|
||||
{
|
||||
internal static class PlatformApis
|
||||
{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright(c) .NET Foundation and contributors.All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.DotNet.InternalAbstractions
|
||||
namespace Microsoft.DotNet.PlatformAbstractions
|
||||
{
|
||||
public enum Platform
|
||||
{
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.DotNet.InternalAbstractions.Native;
|
||||
using Microsoft.DotNet.PlatformAbstractions.Native;
|
||||
|
||||
namespace Microsoft.DotNet.InternalAbstractions
|
||||
namespace Microsoft.DotNet.PlatformAbstractions
|
||||
{
|
||||
public static class RuntimeEnvironment
|
||||
{
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using Microsoft.Extensions.EnvironmentAbstractions;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.DotNet.InternalAbstractions
|
||||
{
|
||||
internal class TemporaryDirectory : ITemporaryDirectory
|
||||
{
|
||||
public string DirectoryPath { get; }
|
||||
|
||||
public TemporaryDirectory()
|
||||
{
|
||||
DirectoryPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
Directory.CreateDirectory(DirectoryPath);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(DirectoryPath, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore failures here.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.DotNet.InternalAbstractions;
|
||||
using Microsoft.DotNet.PlatformAbstractions;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyModel
|
||||
{
|
||||
|
|
|
@ -5,7 +5,6 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.EnvironmentAbstractions;
|
||||
|
||||
#if !NETSTANDARD1_3
|
||||
|
||||
|
|
|
@ -2,9 +2,8 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.IO;
|
||||
using Microsoft.DotNet.InternalAbstractions;
|
||||
|
||||
namespace Microsoft.Extensions.EnvironmentAbstractions
|
||||
namespace Microsoft.Extensions.DependencyModel
|
||||
{
|
||||
internal class DirectoryWrapper: IDirectory
|
||||
{
|
||||
|
@ -12,10 +11,5 @@ namespace Microsoft.Extensions.EnvironmentAbstractions
|
|||
{
|
||||
return Directory.Exists(path);
|
||||
}
|
||||
|
||||
public ITemporaryDirectory CreateTemporaryDirectory()
|
||||
{
|
||||
return new TemporaryDirectory();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Extensions.EnvironmentAbstractions
|
||||
namespace Microsoft.Extensions.DependencyModel
|
||||
{
|
||||
internal class EnvironmentWrapper : IEnvironment
|
||||
{
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Extensions.EnvironmentAbstractions
|
||||
namespace Microsoft.Extensions.DependencyModel
|
||||
{
|
||||
internal class FileSystemWrapper : IFileSystem
|
||||
{
|
|
@ -4,7 +4,7 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.Extensions.EnvironmentAbstractions
|
||||
namespace Microsoft.Extensions.DependencyModel
|
||||
{
|
||||
internal class FileWrapper: IFile
|
||||
{
|
|
@ -1,12 +1,10 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Extensions.EnvironmentAbstractions
|
||||
namespace Microsoft.Extensions.DependencyModel
|
||||
{
|
||||
internal interface IDirectory
|
||||
{
|
||||
bool Exists(string path);
|
||||
|
||||
ITemporaryDirectory CreateTemporaryDirectory();
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Extensions.EnvironmentAbstractions
|
||||
namespace Microsoft.Extensions.DependencyModel
|
||||
{
|
||||
internal interface IEnvironment
|
||||
{
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.Extensions.EnvironmentAbstractions
|
||||
namespace Microsoft.Extensions.DependencyModel
|
||||
{
|
||||
internal interface IFile
|
||||
{
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Extensions.EnvironmentAbstractions
|
||||
namespace Microsoft.Extensions.DependencyModel
|
||||
{
|
||||
internal interface IFileSystem
|
||||
{
|
|
@ -4,8 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.DotNet.InternalAbstractions;
|
||||
using Microsoft.Extensions.EnvironmentAbstractions;
|
||||
using Microsoft.DotNet.PlatformAbstractions;
|
||||
|
||||
#if !NETSTANDARD1_3
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using Microsoft.DotNet.InternalAbstractions;
|
||||
using Microsoft.Extensions.EnvironmentAbstractions;
|
||||
using Microsoft.DotNet.PlatformAbstractions;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyModel.Resolution
|
||||
{
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.EnvironmentAbstractions;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyModel.Resolution
|
||||
{
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.DotNet.InternalAbstractions;
|
||||
using Microsoft.Extensions.EnvironmentAbstractions;
|
||||
using Microsoft.DotNet.PlatformAbstractions;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyModel.Resolution
|
||||
{
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.DotNet.InternalAbstractions;
|
||||
using Microsoft.Extensions.EnvironmentAbstractions;
|
||||
using Microsoft.DotNet.PlatformAbstractions;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyModel.Resolution
|
||||
{
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.EnvironmentAbstractions;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyModel.Resolution
|
||||
{
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"keyFile": "../../tools/Key.snk"
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.DotNet.InternalAbstractions": {
|
||||
"Microsoft.DotNet.PlatformAbstractions": {
|
||||
"target": "project"
|
||||
},
|
||||
"Newtonsoft.Json": "9.0.1"
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using Microsoft.DotNet.TestFramework;
|
||||
using Microsoft.DotNet.Tools.Test.Utilities;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyModel
|
||||
{
|
||||
public class FunctionalTests : TestBase
|
||||
{
|
||||
private readonly string _testProjectsRoot;
|
||||
|
||||
public FunctionalTests()
|
||||
{
|
||||
_testProjectsRoot = Path.Combine(AppContext.BaseDirectory, "TestAssets", "TestProjects");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("TestApp", true)]
|
||||
[InlineData("TestAppPortable", true)]
|
||||
[InlineData("TestAppDeps", false)]
|
||||
[InlineData("TestAppPortableDeps", false)]
|
||||
public void RunTest(string appname, bool checkCompilation)
|
||||
{
|
||||
var testProjectPath = Path.Combine(RepoRoot, "TestAssets", "TestProjects", "DependencyContextValidator", appname);
|
||||
var testProject = Path.Combine(testProjectPath, "project.json");
|
||||
|
||||
var runCommand = new RunCommand(testProject);
|
||||
var result = runCommand.ExecuteWithCapturedOutput();
|
||||
result.Should().Pass();
|
||||
ValidateRuntimeLibraries(result, appname);
|
||||
if (checkCompilation)
|
||||
{
|
||||
ValidateCompilationLibraries(result, appname);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("TestApp", false, true)]
|
||||
[InlineData("TestAppPortable", true, true)]
|
||||
[InlineData("TestAppDeps", false, false)]
|
||||
[InlineData("TestAppPortableDeps", true, false)]
|
||||
public void PublishTest(string appname, bool portable, bool checkCompilation)
|
||||
{
|
||||
var testProjectPath = Path.Combine(RepoRoot, "TestAssets", "TestProjects", "DependencyContextValidator", appname);
|
||||
var testProject = Path.Combine(testProjectPath, "project.json");
|
||||
|
||||
var publishCommand = new PublishCommand(testProject);
|
||||
publishCommand.Execute().Should().Pass();
|
||||
|
||||
var exeName = portable ? publishCommand.GetPortableOutputName() : publishCommand.GetOutputExecutable();
|
||||
|
||||
var result = TestExecutable(publishCommand.GetOutputDirectory(portable).FullName, exeName, string.Empty);
|
||||
ValidateRuntimeLibraries(result, appname);
|
||||
if (checkCompilation)
|
||||
{
|
||||
ValidateCompilationLibraries(result, appname);
|
||||
}
|
||||
}
|
||||
|
||||
[WindowsOnlyFact]
|
||||
public void RunTestFullClr()
|
||||
{
|
||||
var testProjectPath = Path.Combine(RepoRoot, "TestAssets", "TestProjects", "DependencyContextValidator", "TestAppFullClr");
|
||||
var testProject = Path.Combine(testProjectPath, "project.json");
|
||||
|
||||
var runCommand = new RunCommand(testProject);
|
||||
var result = runCommand.ExecuteWithCapturedOutput();
|
||||
result.Should().Pass();
|
||||
ValidateRuntimeLibrariesFullClr(result, "TestAppFullClr");
|
||||
ValidateCompilationLibrariesFullClr(result, "TestAppFullClr");
|
||||
}
|
||||
|
||||
[WindowsOnlyFact]
|
||||
public void PublishTestFullClr()
|
||||
{
|
||||
var testProjectPath = Path.Combine(RepoRoot, "TestAssets", "TestProjects", "DependencyContextValidator", "TestAppFullClr");
|
||||
var testProject = Path.Combine(testProjectPath, "project.json");
|
||||
|
||||
var publishCommand = new PublishCommand(testProject);
|
||||
publishCommand.Execute().Should().Pass();
|
||||
|
||||
var result = TestExecutable(publishCommand.GetOutputDirectory().FullName, publishCommand.GetOutputExecutable(), string.Empty);
|
||||
ValidateRuntimeLibrariesFullClr(result, "TestAppFullClr");
|
||||
ValidateCompilationLibrariesFullClr(result, "TestAppFullClr");
|
||||
}
|
||||
|
||||
private void ValidateRuntimeLibrariesFullClr(CommandResult result, string appname)
|
||||
{
|
||||
// entry assembly
|
||||
result.Should().HaveStdOutContaining($"Runtime {appname.ToLowerInvariant()}:{appname}");
|
||||
// project dependency
|
||||
result.Should().HaveStdOutContaining("Runtime dependencycontextvalidator:DependencyContextValidator");
|
||||
}
|
||||
|
||||
private void ValidateCompilationLibrariesFullClr(CommandResult result, string appname)
|
||||
{
|
||||
// entry assembly
|
||||
result.Should().HaveStdOutContaining($"Compilation {appname.ToLowerInvariant()}:{appname}.exe");
|
||||
// project dependency
|
||||
result.Should().HaveStdOutContaining("Compilation dependencycontextvalidator:DependencyContextValidator.dll");
|
||||
// system assembly
|
||||
result.Should().HaveStdOutContaining("Compilation mscorlib:mscorlib.dll");
|
||||
}
|
||||
|
||||
|
||||
private void ValidateRuntimeLibraries(CommandResult result, string appname)
|
||||
{
|
||||
// entry assembly
|
||||
result.Should().HaveStdOutContaining($"Runtime {appname.ToLowerInvariant()}:{appname}");
|
||||
// project dependency
|
||||
result.Should().HaveStdOutContaining("Runtime dependencycontextvalidator:DependencyContextValidator");
|
||||
// system assembly
|
||||
result.Should().HaveStdOutContainingIgnoreCase("Runtime System.Linq:System.Linq");
|
||||
}
|
||||
|
||||
private void ValidateCompilationLibraries(CommandResult result, string appname)
|
||||
{
|
||||
// entry assembly
|
||||
result.Should().HaveStdOutContaining($"Compilation {appname.ToLowerInvariant()}:{appname}.dll");
|
||||
// project dependency
|
||||
result.Should().HaveStdOutContaining("Compilation dependencycontextvalidator:DependencyContextValidator.dll");
|
||||
// system assembly
|
||||
result.Should().HaveStdOutContainingIgnoreCase("Compilation System.Linq:System.Linq.dll");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,7 +5,8 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using FluentAssertions;
|
||||
using Microsoft.DotNet.InternalAbstractions;
|
||||
using Microsoft.DotNet.PlatformAbstractions;
|
||||
using Microsoft.Extensions.DependencyModel;
|
||||
using Microsoft.Extensions.DependencyModel.Resolution;
|
||||
using Xunit;
|
||||
using F = Microsoft.Extensions.DependencyModel.Tests.TestLibraryFactory;
|
||||
|
|
|
@ -5,7 +5,8 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using FluentAssertions;
|
||||
using Microsoft.DotNet.InternalAbstractions;
|
||||
using Microsoft.DotNet.PlatformAbstractions;
|
||||
using Microsoft.Extensions.DependencyModel;
|
||||
using Microsoft.Extensions.DependencyModel.Resolution;
|
||||
using Xunit;
|
||||
using F = Microsoft.Extensions.DependencyModel.Tests.TestLibraryFactory;
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.DependencyModel;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyModel.Tests
|
||||
{
|
||||
class FileSystemMockBuilder
|
||||
{
|
||||
private Dictionary<string, string> _files = new Dictionary<string, string>();
|
||||
|
||||
internal static IFileSystem Empty { get; } = Create().Build();
|
||||
|
||||
public static FileSystemMockBuilder Create()
|
||||
{
|
||||
return new FileSystemMockBuilder();
|
||||
}
|
||||
|
||||
public FileSystemMockBuilder AddFile(string name, string content = "")
|
||||
{
|
||||
_files.Add(name, content);
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileSystemMockBuilder AddFiles(string basePath, params string[] files)
|
||||
{
|
||||
foreach (var file in files)
|
||||
{
|
||||
AddFile(Path.Combine(basePath, file));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
internal IFileSystem Build()
|
||||
{
|
||||
return new FileSystemMock(_files);
|
||||
}
|
||||
|
||||
private class FileSystemMock : IFileSystem
|
||||
{
|
||||
public FileSystemMock(Dictionary<string, string> files)
|
||||
{
|
||||
File = new FileMock(files);
|
||||
Directory = new DirectoryMock(files);
|
||||
}
|
||||
|
||||
public IFile File { get; }
|
||||
|
||||
public IDirectory Directory { get; }
|
||||
}
|
||||
|
||||
private class FileMock : IFile
|
||||
{
|
||||
private Dictionary<string, string> _files;
|
||||
public FileMock(Dictionary<string, string> files)
|
||||
{
|
||||
_files = files;
|
||||
}
|
||||
|
||||
public bool Exists(string path)
|
||||
{
|
||||
return _files.ContainsKey(path);
|
||||
}
|
||||
|
||||
public string ReadAllText(string path)
|
||||
{
|
||||
string text;
|
||||
if (!_files.TryGetValue(path, out text))
|
||||
{
|
||||
throw new FileNotFoundException(path);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
public Stream OpenRead(string path)
|
||||
{
|
||||
return new MemoryStream(Encoding.UTF8.GetBytes(ReadAllText(path)));
|
||||
}
|
||||
|
||||
public Stream OpenFile(
|
||||
string path,
|
||||
FileMode fileMode,
|
||||
FileAccess fileAccess,
|
||||
FileShare fileShare,
|
||||
int bufferSize,
|
||||
FileOptions fileOptions)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void CreateEmptyFile(string path)
|
||||
{
|
||||
_files.Add(path, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private class DirectoryMock : IDirectory
|
||||
{
|
||||
private Dictionary<string, string> _files;
|
||||
|
||||
public DirectoryMock(Dictionary<string, string> files)
|
||||
{
|
||||
_files = files;
|
||||
}
|
||||
|
||||
public bool Exists(string path)
|
||||
{
|
||||
return _files.Keys.Any(k => k.StartsWith(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -10,12 +10,13 @@
|
|||
"version": "1.0.0"
|
||||
},
|
||||
"System.Diagnostics.TraceSource": "4.0.0",
|
||||
"Microsoft.DotNet.Tools.Tests.Utilities": {
|
||||
"Microsoft.Extensions.DependencyModel": {
|
||||
"target": "project"
|
||||
},
|
||||
"Microsoft.DotNet.Cli.Utils": {
|
||||
"Microsoft.DotNet.PlatformAbstractions": {
|
||||
"target": "project"
|
||||
},
|
||||
"Microsoft.DotNet.InternalAbstractions": "1.0.0",
|
||||
"FluentAssertions": "4.0.0",
|
||||
"moq.netcore": "4.4.0-beta8",
|
||||
"xunit": "2.1.0",
|
||||
|
|
Двоичный файл не отображается.
Загрузка…
Ссылка в новой задаче