static IEnumerable <T> GetReferencesTree <T>(IPackageRepository repo, IPackage package, FrameworkName targetFrameworkName, IndentingLineWriter writer, Func <IPackageAssemblyReference, IPackage, T> selector) { writer?.WriteLine(package.GetFullName()); IEnumerable <IPackageAssemblyReference> refs; if (VersionUtility.TryGetCompatibleItems(targetFrameworkName, package.AssemblyReferences, out refs)) { foreach (var r in refs) { yield return(selector(r, package)); } } else { yield return(selector(null, package)); } var subrefs = from d in package.GetCompatiblePackageDependencies(targetFrameworkName) select repo.FindPackage(d.Id) into dp where dp != null from r in GetReferencesTree(repo, dp, targetFrameworkName, writer?.Indent(), selector) select r; foreach (var r in subrefs) { yield return(r); } }
static void GenerateExecutable(string cscPath, string queryFilePath, string packagesPath, QueryLanguage queryKind, string source, IEnumerable <string> imports, IEnumerable <Reference> references, IndentingLineWriter writer) { // TODO error handling in generated code var body = queryKind == QueryLanguage.Expression ? Seq.Return( "static class UserQuery {", " static void Main() {", " System.Console.WriteLine(", source, ");", " }", "}") : queryKind == QueryLanguage.Program ? Seq.Return( "class UserQuery {", " static int Main(string[] args) {", " new UserQuery().Main(); return 0;", " }", source, "}") : Seq.Return( "class UserQuery {", " static int Main(string[] args) {", " new UserQuery().Main(); return 0;", " }", " void Main() {", source, " }", "}"); var rs = references.ToArray(); var csFilePath = Path.ChangeExtension(queryFilePath, ".cs"); File.WriteAllLines(csFilePath, from lines in new[] { from ns in imports.GroupBy(e => e, StringComparer.Ordinal) select $"using {ns.First()};", body, Seq.Return(string.Empty), } from line in lines select line); var quoteOpt = QuoteOpt(' '); var args = Seq.Return(csFilePath).Concat(rs.Select(r => "/r:" + r.Path)) .Select(quoteOpt); var argsLine = string.Join(" ", args); var workingDirPath = Path.GetDirectoryName(queryFilePath); if (cscPath != null) { if (!File.Exists(cscPath)) { throw new Exception("Invalid path to C# compiler binary: " + cscPath); } } else { var x86ProgramFilesPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86); cscPath = Seq.Return("14.0", "12.0") .Select(v => Path.Combine(x86ProgramFilesPath, "MSBuild", v, "bin", "csc.exe")) .FirstOrDefault(File.Exists); if (cscPath == null) { throw new Exception("Unable to find C# compiler in the expected location(s)."); } } writer.WriteLine(quoteOpt(cscPath) + " " + argsLine); using (var process = Process.Start(new ProcessStartInfo { CreateNoWindow = true, UseShellExecute = false, FileName = cscPath, Arguments = argsLine, RedirectStandardError = true, RedirectStandardOutput = true, WorkingDirectory = string.IsNullOrEmpty(workingDirPath) ? Environment.CurrentDirectory : Path.GetFullPath(workingDirPath), })) { Debug.Assert(process != null); process.OutputDataReceived += OnProcessStdDataReceived(writer.Indent()); process.ErrorDataReceived += OnProcessStdDataReceived(writer.Indent()); process.BeginOutputReadLine(); process.BeginErrorReadLine(); process.WaitForExit(); var exitCode = process.ExitCode; if (exitCode != 0) { throw new Exception($"C# compiler finished with a non-zero exit code of {exitCode}."); } } var queryDirPath = Path.GetFullPath(// ReSharper disable once AssignNullToNotNullAttribute Path.GetDirectoryName(queryFilePath)) + Path.DirectorySeparatorChar; var privatePaths = from r in rs select Path.Combine(queryDirPath, r.Path) into r where File.Exists(r) select Path.GetDirectoryName(r) into d where !string.IsNullOrEmpty(d) select MakeRelativePath(queryDirPath, d + Path.DirectorySeparatorChar) into d where !Path.IsPathRooted(d) group 1 by d into g select g.Key.TrimEnd(PathSeparators); privatePaths = privatePaths.ToArray(); if (privatePaths.Any()) { var asmv1 = XNamespace.Get("urn:schemas-microsoft-com:asm.v1"); var config = new XElement("configuration", new XElement("runtime", new XElement(asmv1 + "assemblyBinding", new XElement(asmv1 + "probing", new XAttribute("privatePath", string.Join(";", privatePaths)))))); File.WriteAllText(Path.ChangeExtension(queryFilePath, ".exe.config"), config.ToString()); } // TODO User-supplied csi.cmd GenerateBatch(LoadTextResource("exe.cmd"), queryFilePath, packagesPath, rs); }
static T Compile <T>(string queryFilePath, IPackageRepository repo, string packagesPath, IEnumerable <PackageReference> extraPackageReferences, IEnumerable <string> extraImports, FrameworkName targetFramework, bool verbose, IndentingLineWriter writer, Func <QueryLanguage, string, IEnumerable <string>, IEnumerable <Reference>, T> selector) { var eomLineNumber = LinqPad.GetEndOfMetaLineNumber(queryFilePath); var lines = File.ReadLines(queryFilePath); var xml = string.Join(Environment.NewLine, // ReSharper disable once PossibleMultipleEnumeration lines.Take(eomLineNumber)); var query = XElement.Parse(xml); if (verbose) { writer.Write(query); } QueryLanguage queryKind; if (!Enum.TryParse((string)query.Attribute("Kind"), true, out queryKind) || (queryKind != QueryLanguage.Statements && queryKind != QueryLanguage.Expression && queryKind != QueryLanguage.Program)) { throw new NotSupportedException("Only LINQPad " + "C# Statements and Expression queries are fully supported " + "and C# Program queries partially in this version."); } var nrs = from nrsq in new[] { from nr in query.Elements("NuGetReference") select new PackageReference((string)nr, SemanticVersion.ParseOptionalVersion((string)nr.Attribute("Version")), (bool?)nr.Attribute("Prerelease") ?? false), extraPackageReferences, } from nr in nrsq select new { nr.Id, nr.Version, nr.IsPrereleaseAllowed, Title = string.Join(" ", Seq.Return(nr.Id, nr.Version?.ToString(), nr.IsPrereleaseAllowed ? "(pre-release)" : null) .Filter()), }; nrs = nrs.ToArray(); if (verbose && nrs.Any()) { writer.WriteLine($"Packages referenced ({nrs.Count():N0}):"); writer.Indent().WriteLines(from nr in nrs select nr.Title); } writer.WriteLine($"Packages directory: {packagesPath}"); var pm = new PackageManager(repo, packagesPath); pm.PackageInstalling += (_, ea) => writer.WriteLine($"Installing {ea.Package}..."); pm.PackageInstalled += (_, ea) => writer.Indent().WriteLine($"Installed at {ea.InstallPath}"); writer.WriteLine($"Packages target: {targetFramework}"); var resolutionList = Enumerable.Repeat(new { Package = default(IPackage), AssemblyPath = default(string) }, 0) .ToList(); foreach (var nr in nrs) { var pkg = pm.LocalRepository.FindPackage(nr.Id, nr.Version, allowPrereleaseVersions: nr.IsPrereleaseAllowed, allowUnlisted: false); if (pkg == null) { pkg = repo.FindPackage(nr.Id, nr.Version, allowPrereleaseVersions: nr.IsPrereleaseAllowed, allowUnlisted: false); if (pkg == null) { throw new Exception("Package not found: " + nr.Title); } pm.InstallPackage(pkg.Id, pkg.Version); } writer.WriteLine("Resolving references..."); resolutionList.AddRange(GetReferencesTree(pm.LocalRepository, pkg, targetFramework, writer.Indent(), (r, p) => new { Package = p, AssemblyPath = r != null ? Path.Combine(pm.PathResolver.GetInstallPath(p), r.Path) : null })); } var packagesPathWithTrailer = packagesPath + Path.DirectorySeparatorChar; var resolution = resolutionList .GroupBy(r => r.Package) .Select(g => g.First()) .Select(r => new { r.Package, AssemblyPath = r.AssemblyPath != null ? MakeRelativePath(queryFilePath, packagesPathWithTrailer) + MakeRelativePath(packagesPathWithTrailer, r.AssemblyPath) : null, }) .Partition(r => r.AssemblyPath == null, (ok, nok) => new { ResolvedReferences = ok, ReferencelessPackages = from r in nok select r.Package.GetFullName(), }); resolution.ReferencelessPackages.StartIter(e => { writer.WriteLine($"Warning! Packages with no references for {targetFramework}:"); writer.Indent().WriteLines(e.ResumeFromCurrent()); }); var references = resolution.ResolvedReferences.ToArray(); references.Select(r => r.AssemblyPath).StartIter(e => { writer.WriteLine($"Resolved references ({references.Length:N0}):"); writer.Indent().WriteLines(e.ResumeFromCurrent()); }); return (selector( queryKind, // ReSharper disable once PossibleMultipleEnumeration string.Join(Environment.NewLine, lines.Skip(eomLineNumber)), LinqPad.DefaultNamespaces .Concat(from ns in query.Elements("Namespace") select(string) ns) .Concat(extraImports), LinqPad.DefaultReferences.Select(r => new Reference(r)) .Concat(from r in query.Elements("Reference") select(string) r into r select r.StartsWith(LinqPad.RuntimeDirToken, StringComparison.OrdinalIgnoreCase) ? r.Substring(LinqPad.RuntimeDirToken.Length) : r into r select new Reference(r)) .Concat(from r in references select new Reference(r.AssemblyPath, r.Package)))); }