脚本引擎 ScriptEngine 设计于2012年,为了配合XCode的动态模型,在内存中动态生成实体类并执行,后来大量用于执行动态表达式,最终拓展为独立产品 XScript 。
Nuget包:NewLife.Core
源码:https://github.com/NewLifeX/X/blob/master/NewLife.Core/Reflection/ScriptEngine.cs
项目:https://github.com/NewLifeX/XScript
安装:https://x.newlifex.com/XScript_Install.exe
单一表达式
应用系统开发过程中,常常遇到需要动态计算公式的业务需求。本脚本引擎实际上是调用.NET编译器,因此表达式上支持C#的全部语法,足够强大,能够满足各种业务需求。
// 无参数快速调用 var n = (Int32)ScriptEngine.Execute("2*3"); // 约定参数快速调用 var n = (Int32)ScriptEngine.Execute("p0*p1", new Object[] { 2, 3 }); // 根据代码创建脚本实例,相同代码只编译一次 var se = ScriptEngine.Create("a+b"); // 如果Method为空说明未编译,可设置参数 if (se.Method == null) { se.Parameters.Add("a", typeof(Int32)); se.Parameters.Add("b", typeof(Int32)); }
常见表达式有直接计算型,或者外部传参型,比较复杂的场合,可以使用第三种传入参数。
多个语句
较为复杂的场景,可能需要写多行C#代码语句。
例如下面这一段用于 XScript 的脚本:
// 自动选择最新的文件源 var srcs = new String[] { @"..\Bin", @"C:\X\DLL", @"C:\X\Bin", @"D:\X\Bin", @"E:\X\DLL", @"E:\X\Bin" }; var cur = ".".GetFullPath(); foreach (var item in srcs) { // 跳过当前目录 if (item.EqualIgnoreCase(cur)) continue; Console.WriteLine("复制 {0} => {1}", item, cur); try { item.AsDirectory().CopyToIfNewer(cur, "*.dll;*.exe;*.xml;*.pdb;*.cs;*.xs", false, name => Console.WriteLine("\t{1}\t{0}", name, item.CombinePath(name).AsFile().LastWriteTime.ToFullString())); } catch (Exception ex) { Console.WriteLine(" " + ex.Message); } }
上述脚本经脚本引擎编译后即可调用执行。实际业务场景就是一些DLL拷贝,需要判断修改时间,比系统命令Copy稍微高级一些。
多个方法
更复杂的场合,就是生成一个类,里面有多个方法,脚本引擎支持编译执行。
包含多个方法的脚本,其中必须有一个公有静态Main函数作为执行入口,入参是String[]。
脚本执行器 XScript
XScript 用于支持编译执行一些功能强大的C#脚本。
下载安装后的主界面如下:
可以直接书写表达式来执行,也可以新建 test.cs 等.cs或.xs结尾的文本文件,在里面写C#代码,然后发送到XScript执行。(.xs文件可以双击执行,但是不利于vscode等编辑器智能识别)
下面是早起用于发布魔方到nuget上的脚本:
//assembly=DLL\NuGet.exe //assembly=System.ComponentModel.DataAnnotations using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml; using System.Xml.Serialization; using NewLife.Log; using NewLife.Xml; using NuGet; namespace NewLife.Reflection { public class ScriptEngine { static void Main() { //PathHelper.BaseDirectory = @"E:\X\Src\NewLife.Cube"; XTrace.Debug = true; XTrace.UseConsole(); var dir = ".".GetBasePath().AsDirectory(); // 查找NuGet.exe var ng = "..\\DLL\\NuGet.exe".GetBasePath(); //"cmd".Run("/c del *.nuspec /f/q"); foreach(var item in dir.GetAllFiles("*.nuspec")) { Console.WriteLine("删除 {0}", item); item.Delete(); } // 找到名称 var proj = dir.FullName.EnsureEnd("\\"); Console.WriteLine("proj项目:{0}", proj); var name = "NewLife.Cube"; Console.WriteLine("项目名称:{0}", name); proj = dir.GetAllFiles("*.csproj").FirstOrDefault().FullName; var spec = name + ".nuspec"; var specFile = spec.GetBasePath(); if (!File.Exists(specFile)) { var tar = "..\\..\\Bin\\" + name + ".dll"; tar = tar.GetBasePath(); if (!File.Exists(tar)) { Console.WriteLine("只能找项目文件了,总得做点啥不是"); //编译当前工程 "msbuild".Run(proj + " /t:Rebuild /p:Configuration=Release /p:VisualStudioVersion=15.0 /noconlog /nologo", 8000); //"NuGet".Run("spec -f -a " + name, 5000); return; } Console.WriteLine("目标 {0}", tar); ng.Run("spec -Force -a " + tar, 5000); /*var sCmd = new NuGet.CommandLine.SpecCommand(); sCmd.Force = true; sCmd.AssemblyPath = tar; sCmd.ExecuteCommand();*/ var spec2 = dir.GetAllFiles(spec).First().Name; if (!spec.EqualIgnoreCase(spec2)) File.Move(spec2, spec); } // 部分项目加上前缀 var name2 = name.EnsureStart("NewLife."); var ms = new MemoryStream(File.ReadAllBytes(specFile)); var cfg = Manifest.ReadFrom(ms, false); // 修改配置文件 cfg.Metadata.Id = name2; cfg.Metadata.LicenseUrl = "http://www.NewLifeX.com"; cfg.Metadata.ProjectUrl = "https://github.com/NewLifeX"; cfg.Metadata.IconUrl = "http://www.NewLifeX.com/favicon.ico"; cfg.Metadata.Copyright = "Copyright 2002-{0} 新生命开发团队 http://www.NewLifeX.com".F(DateTime.Now.Year); cfg.Metadata.Tags = "新生命团队 X组件 NewLife " + name; cfg.Metadata.ReleaseNotes = "https://github.com/NewLifeX"; //cfg.Metadata.Authors="新生命开发团队"; //cfg.Metadata.Owners="新生命开发团队"; /*var rep = new RepositoryMetadata(); rep.Type = "git"; rep.Url = "https://github.com/NewLifeX/X.git"; cfg.Metadata.Repository = rep;*/ // 清空依赖 var dgs = cfg.Metadata.DependencySets; if (dgs != null) dgs.Clear(); //var dgs = cfg.Metadata.DependencyGroups; //dps.RemoveAll(e => e.Id == "SampleDependency"); //var fx = new NuGet.Frameworks.NuGet.Frameworks1094018.NuGetFramework(".NETFramework4.5"); //var dg = new PackageDependencyGroup(fx, new PackageDependency[0]); //dgs.Add(dk); // 自动添加所有文件 if (cfg.Files == null) cfg.Files = new List<ManifestFile>(); cfg.Files.Clear(); if (cfg.Files.Count == 0) { AddFile(cfg, name, "dll;xml;pdb;exe", @"..\..\Bin", @"lib\net45"); AddFile(cfg, name, "dll;xml;pdb;exe", @"..\..\Bin4", @"lib\net40"); AddFile(cfg, name, "dll;xml;pdb;exe", @"..\Bin\netcoreapp3.0", @"lib\netcoreapp3.0"); AddFile(cfg, name + ".Views", "dll;xml;pdb;exe", @"..\Bin\netcoreapp3.0", @"lib\netcoreapp3.0"); if (name == "XCode") AddFile(cfg, null, "*.ps1", @"tools", @"tools"); } ms = new MemoryStream(); cfg.Save(ms); File.WriteAllBytes(specFile, ms.ToArray()); //var pack = "pack {0} -IncludeReferencedProjects -Build -Prop Configuration={1} -Exclude **\\*.txt;**\\*.png;content\\*.xml"; // *\\*.*干掉下级的所有文件 var pack = "pack {0} -IncludeReferencedProjects -Exclude **\\*.txt;**\\*.png;*.jpg"; Console.WriteLine("打包:{0}", proj); //"cmd".Run("/c del *.nupkg /f/q"); foreach(var item in dir.GetAllFiles("*.nupkg")) { Console.WriteLine("删除 {0}", item); item.Delete(); } ng.Run(pack.F(proj), 30000); var fi = dir.GetAllFiles("*.nupkg").FirstOrDefault(); if (fi != null) { var nupkg = fi.Name; Console.WriteLine("发布:{0}", nupkg); ng.Run("push {0} {1} -Source https://www.nuget.org -Verbosity detailed".F(nupkg, File.ReadAllText("..\\..\\nuget.key")), 30000); } } //static ManifestDependency _md; static void AddFile(Manifest cfg, String name, String exts, String src, String target) { if (!name.IsNullOrEmpty()) exts = exts.Split(";").Select(e=>name + "." + e).Join(";"); var fs = src.AsDirectory().GetAllFiles(exts).ToList(); //XTrace.WriteLine("目录:{0} 文件:{1}", src.AsDirectory().FullName, fs.Count); if(fs.Count == 0) return; var dgs = cfg.Metadata.DependencySets; var dg = new ManifestDependencySet(); switch(target.Substring(@"\")) { case "net20": dg.TargetFramework = ".NETFramework2.0"; break; case "net40": dg.TargetFramework = ".NETFramework4.0"; break; case "net45": dg.TargetFramework = ".NETFramework4.5"; break; case "netstandard2.0": dg.TargetFramework = ".NETStandard2.0"; break; case "netcoreapp3.0": dg.TargetFramework = "netcoreapp3.0"; break; } dg.Dependencies = new List<ManifestDependency>(); /*if(_md == null) { var md = new ManifestDependency(); md.Id = "NewLife.Core"; md.Version = "7.0.6706.20578"; md.Exclude = "Build,Analyzers"; _md = md; dg.Dependencies.Add(md); }*/ if(!dg.TargetFramework.IsNullOrEmpty()) dgs.Add(dg); XTrace.WriteLine("目录:{0} 框架:{1} 文件:{2}", src, dg.TargetFramework, fs.Count); foreach(var item in fs) { var mf = new ManifestFile(); mf.Source = item.FullName; mf.Target = @"{0}\{1}".F(target, item.Name); cfg.Files.Add(mf); } } } }
头部两行用于引入程序集和命名空间:
//assembly=DLL\NuGet.exe //assembly=System.ComponentModel.DataAnnotations