From 4bd92651ef4bc8b326f764a4355b84f79d5dd1d9 Mon Sep 17 00:00:00 2001 From: Paul Knopf Date: Fri, 12 Apr 2019 17:11:16 -0400 Subject: [PATCH] Added unit test for extracting runtime. --- appveyor.yml | 2 +- build/travis.linux.sh | 2 +- build/travis.osx.sh | 2 +- src/net/Qml.Net.Tests/Qml.Net.Tests.csproj | 2 + src/net/Qml.Net.Tests/RuntimeManagerTests.cs | 70 +++++++ src/net/Qml.Net.sln.DotSettings | 3 +- src/net/Qml.Net/QmlNetConfig.cs | 2 + .../Qml.Net/Runtimes/RuntimeArchitecture.cs | 9 + src/net/Qml.Net/Runtimes/RuntimeManager.cs | 194 ++++++++++++++++++ 9 files changed, 282 insertions(+), 4 deletions(-) create mode 100644 src/net/Qml.Net.Tests/RuntimeManagerTests.cs create mode 100644 src/net/Qml.Net/Runtimes/RuntimeArchitecture.cs create mode 100644 src/net/Qml.Net/Runtimes/RuntimeManager.cs diff --git a/appveyor.yml b/appveyor.yml index 80fb1322..9552c89b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ image: Visual Studio 2017 before_build: - - ps: Invoke-WebRequest -Uri https://github.com/qmlnet/qt-runtimes/releases/download/releases/qt-5.12.2-de3f7b1-win-x64-dev.tar.gz -OutFile C:\qmlnet-qt.tar.gz + - ps: Invoke-WebRequest -Uri https://github.com/qmlnet/qt-runtimes/releases/download/releases/qt-5.12.2-877b810-win-x64-dev.tar.gz -OutFile C:\qmlnet-qt.tar.gz - cmd: 7z x C:\qmlnet-qt.tar.gz -oC:\ - cmd: 7z x C:\qmlnet-qt.tar -oC:\qmlnet-qt - cmd: rm -r C:\Tools\GitVersion\ diff --git a/build/travis.linux.sh b/build/travis.linux.sh index b37b3079..6f2626c5 100755 --- a/build/travis.linux.sh +++ b/build/travis.linux.sh @@ -6,7 +6,7 @@ QT_DIR=$SCRIPT_DIR/Qt sudo apt-get install -y libgl1-mesa-dev mkdir -p $QT_DIR -wget -O- -q https://github.com/qmlnet/qt-runtimes/releases/download/releases/qt-5.12.2-de3f7b1-linux-x64-dev.tar.gz | tar xpz -C $QT_DIR +wget -O- -q https://github.com/qmlnet/qt-runtimes/releases/download/releases/qt-5.12.2-877b810-linux-x64-dev.tar.gz | tar xpz -C $QT_DIR export PATH=$QT_DIR/qt/bin:$PATH export LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/src/native/output:$QT_DIR/qt/lib diff --git a/build/travis.osx.sh b/build/travis.osx.sh index d347032c..9049bdbc 100755 --- a/build/travis.osx.sh +++ b/build/travis.osx.sh @@ -5,7 +5,7 @@ SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) QT_DIR=$SCRIPT_DIR/Qt mkdir -p $QT_DIR -wget -O- -q https://github.com/qmlnet/qt-runtimes/releases/download/releases/qt-5.12.2-de3f7b1-osx-x64-dev.tar.gz | tar xpz -C $QT_DIR +wget -O- -q https://github.com/qmlnet/qt-runtimes/releases/download/releases/qt-5.12.2-877b810-osx-x64-dev.tar.gz | tar xpz -C $QT_DIR export PATH=$QT_DIR/qt/bin:$PATH export DYLD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/src/native/output:$QT_DIR/qt/lib diff --git a/src/net/Qml.Net.Tests/Qml.Net.Tests.csproj b/src/net/Qml.Net.Tests/Qml.Net.Tests.csproj index 4a49a2f4..b9bbd7af 100644 --- a/src/net/Qml.Net.Tests/Qml.Net.Tests.csproj +++ b/src/net/Qml.Net.Tests/Qml.Net.Tests.csproj @@ -6,7 +6,9 @@ + + diff --git a/src/net/Qml.Net.Tests/RuntimeManagerTests.cs b/src/net/Qml.Net.Tests/RuntimeManagerTests.cs new file mode 100644 index 00000000..4187359b --- /dev/null +++ b/src/net/Qml.Net.Tests/RuntimeManagerTests.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using FluentAssertions; +using Qml.Net.Runtimes; +using SharpCompress.Common; +using SharpCompress.Readers; +using Xunit; + +namespace Qml.Net.Tests +{ + public class RuntimeManagerTests : IDisposable + { + private readonly string _tempDirectory; + + public RuntimeManagerTests() + { + _tempDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString().Replace("-", "")); + Directory.CreateDirectory(_tempDirectory); + + RuntimeManager.ExtractTarGZStream = (stream, directory) => + { + using (var reader = ReaderFactory.Open(stream, new ReaderOptions())) + { + while (reader.MoveToNextEntry()) + { + if (!reader.Entry.IsDirectory) + { + reader.WriteEntryToDirectory(directory, new ExtractionOptions() + { + ExtractFullPath = true, + Overwrite = true, + WriteSymbolicLink = (sourcePath, targetPath) => + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + throw new Exception("File links aren't supported."); + } + + var link = new Mono.Unix.UnixSymbolicLinkInfo(sourcePath); + if (File.Exists(sourcePath)) + { + link.Delete(); // equivalent to ln -s -f + } + + link.CreateSymbolicLinkTo(targetPath); + } + }); + } + } + } + }; + } + + [Fact] + public void Can_download_runtime() + { + RuntimeManager.DownloadRuntimeToDirectory(QmlNetConfig.QtBuildVersion, RuntimeTarget.Windows64, _tempDirectory); + File.ReadAllText(Path.Combine(_tempDirectory, "version.txt")).Should().Be($"{QmlNetConfig.QtBuildVersion}-win-x64"); + } + + public void Dispose() + { + if (Directory.Exists(_tempDirectory)) + { + Directory.Delete(_tempDirectory, true); + } + } + } +} \ No newline at end of file diff --git a/src/net/Qml.Net.sln.DotSettings b/src/net/Qml.Net.sln.DotSettings index df2308cb..72ea2e23 100644 --- a/src/net/Qml.Net.sln.DotSettings +++ b/src/net/Qml.Net.sln.DotSettings @@ -1,3 +1,4 @@  GC - OSX \ No newline at end of file + OSX + True \ No newline at end of file diff --git a/src/net/Qml.Net/QmlNetConfig.cs b/src/net/Qml.Net/QmlNetConfig.cs index e4f5d58f..344a621a 100644 --- a/src/net/Qml.Net/QmlNetConfig.cs +++ b/src/net/Qml.Net/QmlNetConfig.cs @@ -4,6 +4,8 @@ namespace Qml.Net { public class QmlNetConfig { + public static string QtBuildVersion => "qt-5.12.2-877b810"; + public static bool ListenForExceptionsWhenInvokingTasks { get; set; } public static event Action UnhandledTaskException; diff --git a/src/net/Qml.Net/Runtimes/RuntimeArchitecture.cs b/src/net/Qml.Net/Runtimes/RuntimeArchitecture.cs new file mode 100644 index 00000000..7d9eec0a --- /dev/null +++ b/src/net/Qml.Net/Runtimes/RuntimeArchitecture.cs @@ -0,0 +1,9 @@ +namespace Qml.Net.Runtimes +{ + public enum RuntimeTarget + { + LinuxX64, + OSX64, + Windows64 + } +} \ No newline at end of file diff --git a/src/net/Qml.Net/Runtimes/RuntimeManager.cs b/src/net/Qml.Net/Runtimes/RuntimeManager.cs new file mode 100644 index 00000000..a485a268 --- /dev/null +++ b/src/net/Qml.Net/Runtimes/RuntimeManager.cs @@ -0,0 +1,194 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Qml.Net.Runtimes +{ + public static class RuntimeManager + { + public delegate string BuildRuntimeUrlDelegate(string qtVersion, RuntimeTarget target); + + // ReSharper disable once MemberCanBePrivate.Global + // ReSharper disable once FieldCanBeMadeReadOnly.Global + public static BuildRuntimeUrlDelegate BuildRuntimeUrl = (qtVersion, target) => + { + var url = $"https://github.com/qmlnet/qt-runtimes/releases/download/releases/{qtVersion}-{{target}}-runtime.tar.gz"; + switch (target) + { + case RuntimeTarget.Windows64: + return url.Replace("{target}", "win-x64"); + case RuntimeTarget.LinuxX64: + return url.Replace("{target}", "linux-x64"); + case RuntimeTarget.OSX64: + return url.Replace("{target}", "osx-64"); + default: + throw new Exception($"Unknown target {target}"); + } + }; + + public delegate void ExtractTarGZStreamDelegate(Stream stream, string destinationDirectory); + + public static ExtractTarGZStreamDelegate ExtractTarGZStream; + + public static RuntimeTarget GetCurrentRuntimeTarget() + { + if (IntPtr.Size != 8) + { + throw new Exception("Only 64bit supported"); + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return RuntimeTarget.Windows64; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return RuntimeTarget.LinuxX64; + } + + if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return RuntimeTarget.OSX64; + } + + throw new Exception("Unknown OS platform"); + } + + public static void DownloadRuntimeToDirectory(string qtVersion, + RuntimeTarget runtimeTarget, + string destinationDirectory) + { + var extractTarGZStreamDel = ExtractTarGZStream; + if (extractTarGZStreamDel == null) + { + throw new Exception("You must set RuntimeManager.ExtractTarGZStream to properly extract a tar file."); + } + + if (!Directory.Exists(destinationDirectory)) + { + throw new Exception($"The directory \"{destinationDirectory}\" doesn't exist."); + } + + if (Directory.GetFiles(destinationDirectory).Length > 0) + { + throw new Exception("The directory is not empty"); + } + + if (Directory.GetDirectories(destinationDirectory).Length > 0) + { + throw new Exception("The directory is not empty"); + } + + var url = BuildRuntimeUrl(qtVersion, runtimeTarget); + + GetUrlStream(url, stream => + { + extractTarGZStreamDel(stream, destinationDirectory); + }); + } + + public static void GetUrlStream(string url, Action action) + { + var syncContext = SynchronizationContext.Current; + try + { + SynchronizationContext.SetSynchronizationContext(null); + + using (var httpClient = new HttpClient()) + { + action(httpClient.GetStreamAsync(url).GetAwaiter().GetResult()); + } + } + finally + { + SynchronizationContext.SetSynchronizationContext(syncContext); + } + } + +// +// private static string GetRuntimeContainerDirectory() +// { +// var homeDirectory = (Environment.OSVersion.Platform == PlatformID.Unix || +// Environment.OSVersion.Platform == PlatformID.MacOSX) +// ? Environment.GetEnvironmentVariable("HOME") +// : Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%"); +// var runtimeDirector = Path.Combine(homeDirectory, ".qmlnet-runtimes"); +// if (!Directory.Exists(runtimeDirector)) +// { +// +// } +// } +// +// public static string GetRuntimeDirectory() +// { +// var runtimePath = Environment.GetEnvironmentVariable("QMLNET_QT_RUNTIME_DIR"); +// if (!string.IsNullOrEmpty(runtimePath)) +// { +// // There is already one ready for us to start using! +// return runtimePath; +// } +// +// // We must now detect the proper version, download it, and return it's path. +// var url = $"https://github.com/qmlnet/qt-runtimes/releases/download/releases/{QmlNetConfig.QtBuildVersion}-{GetPlatformIdentifier()}-runtime.tar.gz"; +// } + + public static void ConfigureRuntimeDirectory(string directory) + { + if (string.IsNullOrEmpty(directory)) + { + throw new ArgumentNullException(nameof(directory)); + } + + if (!Directory.Exists(directory)) + { + throw new Exception("The directory doesn't exist."); + } + + var versionFile = Path.Combine(directory, "version.txt"); + + if (!File.Exists(versionFile)) + { + throw new Exception("The version.txt file doesn't exist in the directory."); + } + + var version = File.ReadAllText(versionFile).TrimEnd(Environment.NewLine.ToCharArray()); + var expectedVersion = $"{QmlNetConfig.QtBuildVersion}-{GetCurrentRuntimeTarget()}"; + + if (version != expectedVersion) + { + throw new Exception($"The version of the runtime directory was {versionFile}, but expected {expectedVersion}"); + } + + var pluginsDirectory = Path.Combine(directory, "plugins"); + if (!Directory.Exists(pluginsDirectory)) + { + throw new Exception($"Plugins directory didn't exist: {pluginsDirectory}"); + } + Environment.SetEnvironmentVariable("QT_PLUGIN_PATH", pluginsDirectory); + + var qmlDirectory = Path.Combine(directory, "qml"); + if (!Directory.Exists(qmlDirectory)) + { + throw new Exception($"QML directory didn't exist: {qmlDirectory}"); + } + Environment.SetEnvironmentVariable("QML2_IMPORT_PATH", qmlDirectory); + + /*if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + if (!string.IsNullOrEmpty(libDirectory) && Directory.Exists(libDirectory)) + { + // Even though we opened up the native dll correctly, we need to add + // the folder to the path. The reason is because QML plugins aren't + // in the same directory and have trouble finding dependencies + // that are within our lib folder. + Environment.SetEnvironmentVariable( + "PATH", + Environment.GetEnvironmentVariable("PATH") + $";{libDirectory}"); + } + }*/ + } + } +} \ No newline at end of file