Skip to content

Commit

Permalink
WIP (fixes some of the issues with multiple versions of same assembly)
Browse files Browse the repository at this point in the history
  • Loading branch information
John Leidegren committed May 24, 2019
1 parent 469b2f1 commit a556fbf
Show file tree
Hide file tree
Showing 19 changed files with 369 additions and 356 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ obj
*.user
node_modules
*.PublishSettings
*_*
1 change: 1 addition & 0 deletions CloudPad.FunctionApp/CloudPad.FunctionApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<ItemGroup>
<Reference Include="LINQPad">
<HintPath>C:\Program Files (x86)\LINQPad5\LINQPad.exe</HintPath>
<Private>true</Private>
</Reference>
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
Expand Down
13 changes: 12 additions & 1 deletion CloudPad.FunctionApp/HttpTrigger.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using CloudPad.Internal;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

Expand All @@ -22,7 +24,16 @@ public static async Task<HttpResponseMessage> Run(
arguments.AddArgument(typeof(TraceWriter), log);
arguments.AddArgument(typeof(ILogger), new TraceWriterLogger(log));

var result = await func.InvokeAsync(arguments, log);
object result;
try {
result = await func.InvokeAsync(arguments, log);
} catch (ArgumentException ex) {
// special sauce for HTTP trigger
log.Error(ex.Message, ex);
return req.CreateResponse(HttpStatusCode.BadRequest, new { ok = false, message = ex.Message });
} catch {
throw;
}

var taskWithValue = result as Task<HttpResponseMessage>;
if (taskWithValue != null) {
Expand Down
1 change: 1 addition & 0 deletions CloudPad/CommandLine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class Options {
public bool compile;
public bool publish;
public bool prepare;
public bool install;

public string out_dir;
}
Expand Down
72 changes: 72 additions & 0 deletions CloudPad/FirstRun.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace CloudPad {
static class FirstRun {
public static string Lockfile => Path.Combine(Env.GetLocalAppDataDirectory(), "first_run");

public static bool ShouldPrompt() {
if (Environment.UserInteractive) {
if (!File.Exists(Lockfile)) {
return true;
}
}
return false;
}

public static void Prompt() {
var mbType = Type.GetType("System.Windows.Forms.MessageBox, System.Windows.Forms");

var show = mbType.GetMethod("Show", BindingFlags.Public | BindingFlags.Static, null, new[] {
typeof(string),
typeof(string),
Type.GetType("System.Windows.Forms.MessageBoxButtons, System.Windows.Forms"),
Type.GetType("System.Windows.Forms.MessageBoxIcon, System.Windows.Forms"),
}, null);

var result = show.Invoke(null, new object[] {
"Looks like this is your first run. Would you like to add a Explorer context menu item to help with deployment of scripts to Azure?",
"Welcome to CloudPad!",
4, // YesNo
0x20 // Question
});

if (Convert.ToInt32(result) == 6) { // Yes
var startInfo = new ProcessStartInfo();

startInfo.FileName = @"C:\Program Files (x86)\LINQPad5\LPRun.exe";
startInfo.Arguments = $"\"{Util.CurrentQueryPath}\" -install";
startInfo.UseShellExecute = true;
startInfo.Verb = "runas";
#if !DEBUG
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
#endif

using (var p = Process.Start(startInfo)) {
p.WaitForExit();
}
}

File.WriteAllText(Lockfile, "");
}

public static void Install() {
using (var shell = Registry.ClassesRoot.OpenSubKey(@"LINQPad\shell", true)) {
using (var publish = shell.CreateSubKey("publish", true)) {
publish.SetValue("", "Publish LINQPad script to Azure");
publish.SetValue("Icon", @"C:\\Program Files (x86)\\LINQPad5\\LINQPad.EXE,0");
using (var command = publish.CreateSubKey("command", true)) {
command.SetValue("", "\"C:\\Program Files (x86)\\LINQPad5\\LPRun.EXE\" \"%1\" -publish");
}
}
}
}
}
}
3 changes: 0 additions & 3 deletions CloudPad/InternalsVisibleTo.cs

This file was deleted.

9 changes: 9 additions & 0 deletions CloudPad/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public static async Task<int> MainAsync(object userQuery, string[] args) {
var LPRun = false;
if ("LPRun.exe".Equals(Process.GetCurrentProcess().MainModule.ModuleName, StringComparison.OrdinalIgnoreCase)) {
LPRun = true;

// pipe trace to console
Trace.Listeners.Add(new ConsoleTraceListener());
}

Expand All @@ -65,6 +67,10 @@ public static async Task<int> MainAsync(object userQuery, string[] args) {
if (args.Length == 0) {
// todo: storage emulator?

if (FirstRun.ShouldPrompt()) {
FirstRun.Prompt();
}

var workingDirectory = Path.Combine(Env.GetLocalAppDataDirectory(), currentQueryPathInfo.InstanceId);

FunctionApp.Deploy(workingDirectory);
Expand Down Expand Up @@ -110,6 +116,9 @@ public static async Task<int> MainAsync(object userQuery, string[] args) {
Compiler.Compile(userQueryInfo, currentQueryInfo, compilationOptions, currentQueryInfo);
Trace.WriteLine($"Done. Output written to '{compilationOptions.OutDir}'");
return 0;
} else if(options.install) {
FirstRun.Install();
return 0;
}
} catch (Exception ex) {
if (Environment.UserInteractive) {
Expand Down
4 changes: 4 additions & 0 deletions CloudPad/Properties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
using System.Reflection;
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("CloudPad.FunctionApp")]
72 changes: 72 additions & 0 deletions CloudPad/internal/AssemblyBindingConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Reflection;

namespace CloudPad.Internal {
class AssemblyBindingRedirect {
public Version OldMinVersion { get; set; }
public Version OldMaxVersion { get; set; }

public Version NewVersion { get; set; }
}

class AssemblyBindingConfig {
public static AssemblyBindingConfig LoadFrom(string path) {
var config = new AssemblyBindingConfig();

var funcConfig = System.Xml.Linq.XElement.Load(path);
var runtime = funcConfig.Element("runtime");

System.Xml.Linq.XNamespace ns = "urn:schemas-microsoft-com:asm.v1";
var assemblyBinding = runtime.Element(ns + "assemblyBinding");
var assemblyIdentityName = ns + "assemblyIdentity";
var bindingRedirectName = ns + "bindingRedirect";
foreach (var dependentAssembly in assemblyBinding.Elements(ns + "dependentAssembly")) {
var assemblyIdentity = dependentAssembly.Element(assemblyIdentityName);

var fullName = (string)assemblyIdentity.Attribute("name");

if ((string)assemblyIdentity.Attribute("culture") != null) {
fullName += ", Culture=" + (string)assemblyIdentity.Attribute("culture");
}

if ((string)assemblyIdentity.Attribute("publicKeyToken") != null) {
fullName += ", PublicKeyToken=" + (string)assemblyIdentity.Attribute("publicKeyToken");
}

var assemblyName = new AssemblyName(fullName);

var bindingRedirect = dependentAssembly.Element(bindingRedirectName);

var oldVersion = ((string)bindingRedirect.Attribute("oldVersion")).Split('-');
var newVerison = (string)bindingRedirect.Attribute("newVersion");

var binding = new AssemblyBindingRedirect {
OldMinVersion = new Version(oldVersion[0]),
OldMaxVersion = new Version(oldVersion[1]),
NewVersion = new Version(newVerison),
};

config.Add(assemblyName.Name, binding);
}

return config;
}

private Dictionary<string, List<AssemblyBindingRedirect>> config = new Dictionary<string, List<AssemblyBindingRedirect>>();

public void Add(string name, AssemblyBindingRedirect binding) {
if (!config.TryGetValue(name, out var bindings)) {
config.Add(name, bindings = new List<AssemblyBindingRedirect>());
}
bindings.Add(binding);
}

public AssemblyBindingRedirect Find(AssemblyName name) {
if (config.TryGetValue(name.Name, out var bindings)) {
return bindings.Find(binding => binding.OldMinVersion <= name.Version && name.Version <= binding.OldMaxVersion);
}
return null;
}
}
}
48 changes: 48 additions & 0 deletions CloudPad/internal/AssemblyCandidateSet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace CloudPad.Internal {
class AssemblyCandidate {
public string FullName => Name.FullName;
public AssemblyName Name { get; set; }
public string Location { get; set; }
public string Source { get; set; }
}

class AssemblyCandidateSet {
private readonly Dictionary<string, List<AssemblyCandidate>> _d = new Dictionary<string, List<AssemblyCandidate>>(StringComparer.OrdinalIgnoreCase);

public IEnumerable<KeyValuePair<string, List<AssemblyCandidate>>> Set {
get {
return _d.OrderBy(x => x.Key); // sort is just to make debugging easier
}
}

public void Add(string location, AssemblyName name, string source) {
if (!_d.TryGetValue(name.Name, out var list)) {
_d.Add(name.Name, list = new List<AssemblyCandidate>());
}

var candidiate = list.Find(c => c.FullName == name.FullName);
if (candidiate == null) {
list.Add(new AssemblyCandidate { Location = location, Name = name, Source = source });
}
}

public bool Unref(AssemblyName name) {
if (_d.TryGetValue(name.Name, out var list)) {
var candidiate = list.FindIndex(c => c.FullName == name.FullName);
if (candidiate != -1) {
list.RemoveAt(candidiate);
if (list.Count == 0) {
_d.Remove(name.Name);
}
return true;
}
}
return false;
}
}
}
Loading

0 comments on commit a556fbf

Please sign in to comment.