From 7d2a68223d85ae66792e3de44e77292c3eaf2bf9 Mon Sep 17 00:00:00 2001 From: Mike Nolan Date: Thu, 16 Apr 2026 18:52:24 -0500 Subject: [PATCH] add prebuild to build tool --- Templates/compiletool/.crossarchiveignore | 1 + Templates/console/.crossarchiveignore | 1 + Templates/emptyweb/.crossarchiveignore | 1 + Templates/lib/.crossarchiveignore | 1 + .../src/buildtool.tcross | 120 ++++-- .../src/sharedviewfs.tcross | 65 +++ Tesses.CrossLang.Shell/src/default.tcross | 2 + Tesses.CrossLang.Shell/src/main.tcross | 3 + Tesses.CrossLang.Shell/src/test.tcross | 386 ++++++++++++++++++ vscode-extension/src/api.ts | 1 + .../syntaxes/crosslang.tmLanguage.json | 2 +- 11 files changed, 554 insertions(+), 29 deletions(-) create mode 100644 Tesses.CrossLang.BuildEssentials/src/sharedviewfs.tcross create mode 100644 Tesses.CrossLang.Shell/src/test.tcross diff --git a/Templates/compiletool/.crossarchiveignore b/Templates/compiletool/.crossarchiveignore index 1746e32..6bcf8b1 100644 --- a/Templates/compiletool/.crossarchiveignore +++ b/Templates/compiletool/.crossarchiveignore @@ -1,2 +1,3 @@ bin obj +.DS_Store \ No newline at end of file diff --git a/Templates/console/.crossarchiveignore b/Templates/console/.crossarchiveignore index 1746e32..6bcf8b1 100644 --- a/Templates/console/.crossarchiveignore +++ b/Templates/console/.crossarchiveignore @@ -1,2 +1,3 @@ bin obj +.DS_Store \ No newline at end of file diff --git a/Templates/emptyweb/.crossarchiveignore b/Templates/emptyweb/.crossarchiveignore index 1746e32..6bcf8b1 100644 --- a/Templates/emptyweb/.crossarchiveignore +++ b/Templates/emptyweb/.crossarchiveignore @@ -1,2 +1,3 @@ bin obj +.DS_Store \ No newline at end of file diff --git a/Templates/lib/.crossarchiveignore b/Templates/lib/.crossarchiveignore index 1746e32..6bcf8b1 100644 --- a/Templates/lib/.crossarchiveignore +++ b/Templates/lib/.crossarchiveignore @@ -1,2 +1,3 @@ bin obj +.DS_Store \ No newline at end of file diff --git a/Tesses.CrossLang.BuildEssentials/src/buildtool.tcross b/Tesses.CrossLang.BuildEssentials/src/buildtool.tcross index fb3e167..e36e6e1 100644 --- a/Tesses.CrossLang.BuildEssentials/src/buildtool.tcross +++ b/Tesses.CrossLang.BuildEssentials/src/buildtool.tcross @@ -26,7 +26,7 @@ class Tesses.CrossLang.BuildTool { var dep = this.PackageManager.GetPackage(name,version); if(TypeOf(dep) == "Null") throw $"Package {name} with version {version} does not exist"; - + var pkgPath = dir / $"{name}-{version}.crvm"; @@ -36,7 +36,7 @@ class Tesses.CrossLang.BuildTool strm.Close(); var strm = FS.Local.OpenFile(pkgPath,"rb"); - + var package = VM.LoadExecutable(strm); strm.Close(); @@ -64,10 +64,10 @@ class Tesses.CrossLang.BuildTool { var dir = FS.MakeFull(projectDirectory); var dirStr = dir.ToString(); - - - - + + + + each(var item : this.DirectoriesCompiled) { if(item.Path == dirStr) return item.Data; @@ -88,7 +88,7 @@ class Tesses.CrossLang.BuildTool var icon = ""; if(TypeOf(configData.compTime) != "Undefined") compTime = configData.compTime; - + if(compTime == "full" && !this.AllowFullCompTime) throw { Type="CompTimeException", @@ -98,12 +98,12 @@ class Tesses.CrossLang.BuildTool if(TypeOf(configData.name) != "Undefined") name = configData.name; - + if(TypeOf(configData.version) != "Undefined") version = configData.version; if(TypeOf(configData.bin_directory) != "Undefined") outputDir = configData.bin_directory; - + if(TypeOf(configData.obj_directory) != "Undefined") objDir = configData.obj_directory; if(TypeOf(configData.source_directory) != "Undefined") @@ -117,11 +117,12 @@ class Tesses.CrossLang.BuildTool + FS.Local.CreateDirectory(dir / outputDir); - if(TypeOf(info.type) == "String" && info.type == "template" || info.type == "archive") + if(TypeOf(info.type) == "String" && (info.type == "template" || info.type == "archive")) { - + //vfs, strm, name, version, info var subdir = new SubdirFilesystem(FS.Local,dir); var output = $"{name}-{version}.crvm"; @@ -149,13 +150,18 @@ class Tesses.CrossLang.BuildTool } FS.Local.CreateDirectory(dir / objDir / "packages"); + FS.Local.CreateDirectory(dir / objDir / "res"); FS.Local.CreateDirectory(dir/resDir); - + + const svfs = new Tesses.CrossLang.SharedViewFilesystem(); + svfs.AddFS(new SubdirFilesystem(FS.Local, dir / resDir)); + svfs.AddFS(new SubdirFilesystem(FS.Local, dir / objDir / "res")); + var dependencies = []; if(TypeOf(configData.project_dependencies) == "List") { - each(var dep : configData.project_dependencies) + each(var dep : configData.project_dependencies) { if(Path.FromString(dep).IsRelative()) { @@ -168,18 +174,18 @@ class Tesses.CrossLang.BuildTool var sources = []; if(TypeOf(configData.dependencies) == "List") { - each(var dep : configData.dependencies) + each(var dep : configData.dependencies) { dependencies.Add(this.GetPackageDependencies(dep.name,dep.version,dir / objDir / "packages")); } } - + each(var item : this.DirectoriesCompiled) { if(item.Path == dirStr) return item.Data; } - + func walk_for_compiling(item,dir2) { @@ -201,18 +207,20 @@ class Tesses.CrossLang.BuildTool env.LoadFileWithDependencies(FS.Local,newFile); env.GetDictionary().RunTool({ + ProjectDirectory = dir, Project = new SubdirFilesystem(FS.Local, dir), + ProjectJson = configData, ProjectInfo = info, GeneratedSource = sources, Config = this.Config }); } - else + else { this.copyFile(item.Output, dir2 / $"{item.Name}-{item.Version}.crvm"); each(var item2 : item.Dependencies) { - walk_for_compiling(item2, dir2); + walk_for_compiling(item2, dir2); } } } @@ -220,9 +228,65 @@ class Tesses.CrossLang.BuildTool var file_deps = []; var file_tools = []; + if(TypeIsList(configData.prebuild)) + { + //do the prebuild things + + each(var job : configData.prebuild) + { + if(TypeIsDictionary(job)) + { + if(TypeIsString(job.condition)) + { + if(!VM.Eval($"return {job.condition};")) + continue; + } + var workdir = dir; + if(TypeIsString(job.workdir)) + workdir /= job.workdir; + if(TypeIsList(job.commands)) + each(var cmd : job.commands) + { + if(TypeIsList(cmd) && cmd.Length > 0) + { + const name = cmd[0]; + const path = Env.GetRealExecutablePath(name).ToString(); + cmd.RemoveAt(0); + var p= Process.Start({ + FileName = path, + Arguments = cmd, + WorkingDirectory = workdir + }); + const result = p.Join(); + if(result != 0) + throw $"process: {name}, did not indicate success: {result}"; + } + } + + if(TypeIsList(job.res)) + { + each(var reso : job.res) + { + if(TypeIsString(reso)) + { + svfs.AddFS(new SubdirFilesystem(FS.Local, dir / reso)); + } + else if(TypeIsDictionary(reso) && TypeIsString(reso.src) && TypeOfString(reso.dest)) + { + const mountable = new MountableFilesystem(new Filesystem({})); + mountable.Mount(reso.dest,new SubdirFilesystem(FS.Local, dir / reso.src)); + svfs.AddFS(mountable); + + } + } + } + } + } + } + each(var dep : dependencies) { - if(dep.Info.type == "lib") + if(dep.Info.type == "lib") { file_deps.Add({ Name = dep.Name, @@ -231,7 +295,7 @@ class Tesses.CrossLang.BuildTool } else if(dep.Info.type == "compile_tool") { - + file_tools.Add({ Name = dep.Name, Version = dep.Version @@ -243,6 +307,7 @@ class Tesses.CrossLang.BuildTool func walk_for_source(sourceDir) { + if(FS.Local.DirectoryExists(sourceDir)) each(var file : FS.Local.EnumeratePaths(sourceDir)) { if(FS.Local.RegularFileExists(file) && file.GetExtension()==".tcross") @@ -273,7 +338,7 @@ class Tesses.CrossLang.BuildTool { //dir / outputDir; - + var exec = Env.GetRealExecutablePath("git"); var git_hash = ""; @@ -321,9 +386,9 @@ class Tesses.CrossLang.BuildTool compTimeEnv.RegisterJson(); compTimeEnv.RegisterRoot(); compTimeEnv.RegisterIO(false); - + dict.FS.Local = new SubdirFilesystem(FS.Local,dir); - + break; case "full": compTimeEnv.RegisterEverything(); @@ -337,7 +402,7 @@ class Tesses.CrossLang.BuildTool compTimeEnv.RegisterJson(); compTimeEnv.RegisterRoot(); compTimeEnv.RegisterIO(false); - + break; } compTimeEnv.LockRegister(); @@ -347,7 +412,7 @@ class Tesses.CrossLang.BuildTool } } - + var result = VM.Compile({ Name = name, Version = version, @@ -355,7 +420,7 @@ class Tesses.CrossLang.BuildTool Info = Json.Encode(info), Icon = icon, Tools = file_tools, - ResourceFileSystem = new SubdirFilesystem(FS.Local, dir / resDir), + ResourceFileSystem = svfs, Dependencies = file_deps, Output = outFile, CompTime = compTimeEnv, @@ -379,11 +444,10 @@ class Tesses.CrossLang.BuildTool Path = dirStr, Data = myData }); - return myData; } return null; } - + } diff --git a/Tesses.CrossLang.BuildEssentials/src/sharedviewfs.tcross b/Tesses.CrossLang.BuildEssentials/src/sharedviewfs.tcross new file mode 100644 index 0000000..b9edb88 --- /dev/null +++ b/Tesses.CrossLang.BuildEssentials/src/sharedviewfs.tcross @@ -0,0 +1,65 @@ +func New.Tesses.CrossLang.SharedViewFilesystem() +{ + const fileSystems = []; + func enumeratePaths (path) { + var found = []; + + each(var fs : fileSystems) + { + if(fs.DirectoryExists(path)) + { + each(var ent : fs.EnumeratePaths(path)) + { + const name = ent.GetFileName(); + if(found.Contains(name)) continue; + found.Add(name); + + } + } + } + + return found; + } + + const fs_dict = { + OpenFile = (path, mode) => { + if(mode == "r" || mode == "rb") + { + each(var fs : fileSystems) + { + if(fs.FileExists(path)) + return fs.OpenFile(path,mode); + } + } + return null; + }, + EnumeratePaths = enumeratePaths, + RegularFileExists = (path) => { + each(var fs : fileSystems) + { + if(fs.RegularFileExists(path)) return true; + } + return false; + }, + FileExists = (path) => { + each(var fs : fileSystems) + { + if(fs.FileExists(path)) return true; + } + return false; + }, + DirectoryExists = (path) => { + each(var fs : fileSystems) + { + if(fs.DirectoryExists(path)) return true; + } + return false; + }, + AddFS = (fs)=>{ + fileSystems.Add(fs); + } + }; + + + return new Filesystem(fs_dict); +} diff --git a/Tesses.CrossLang.Shell/src/default.tcross b/Tesses.CrossLang.Shell/src/default.tcross index 43132f6..c64a508 100644 --- a/Tesses.CrossLang.Shell/src/default.tcross +++ b/Tesses.CrossLang.Shell/src/default.tcross @@ -5,6 +5,7 @@ func Tesses.CrossLang.Shell.Default(dd) if(flag == "version") { Console.WriteLine($"VM version: {VM.RuntimeVersion}"); + Console.WriteLine($"Bytecode version: {VM.BytecodeVersion}"); Console.WriteLine($"Shell version: {main.File.Version}"); Console.WriteLine($"Args version: {Tesses.CrossLang.Args.File.Version}"); Console.WriteLine($"BuildTool version: {Tesses.CrossLang.BuildTool.File.Version}"); @@ -15,6 +16,7 @@ func Tesses.CrossLang.Shell.Default(dd) Console.WriteLine("COMMANDS:"); Console.WriteLine("new: create new project"); Console.WriteLine("build: build a project"); + Console.WriteLine("test: test a project"); Console.WriteLine("run: run a project"); Console.WriteLine("debug: debug a project"); Console.WriteLine("install-console: install a console application"); diff --git a/Tesses.CrossLang.Shell/src/main.tcross b/Tesses.CrossLang.Shell/src/main.tcross index 64fc916..0030e53 100644 --- a/Tesses.CrossLang.Shell/src/main.tcross +++ b/Tesses.CrossLang.Shell/src/main.tcross @@ -80,6 +80,9 @@ func main(args) case "publish": return Tesses.CrossLang.Shell.Publish(dd); break; + case "test": + return Tesses.CrossLang.Shell.Test(dd); + break; default: return Tesses.CrossLang.Shell.Default(dd); } diff --git a/Tesses.CrossLang.Shell/src/test.tcross b/Tesses.CrossLang.Shell/src/test.tcross new file mode 100644 index 0000000..f093c6d --- /dev/null +++ b/Tesses.CrossLang.Shell/src/test.tcross @@ -0,0 +1,386 @@ +class ExpectObj { + private inverted=false; + private currentSpec; + private currentTest; + private left; + public getNot() { + this.inverted = !this.inverted; + + return this; + } + + public ExpectObj(currentTest,currentSpec,left) + { + this.currentTest = currentTest; + this.currentSpec = currentSpec; + this.left = left; + } + public ToBe(right) + { + if(this.inverted) + { + this.SetRes(this.left != right, $"{this.left} is equal to {this.right}"); + } + else + { + + this.SetRes(this.left == right, $"{this.left} is not equal to {right}"); + } + } + public LessThan(right) + { + if(this.inverted) + { + this.SetRes(!(this.left < right), $"{this.left} is less than {this.right}"); + } + else + { + + this.SetRes(this.left < right, $"{this.left} is not less than {right}"); + } + } + public GreaterThan(right) + { + if(this.inverted) + { + this.SetRes(!(this.left > right), $"{this.left} is greater than {this.right}"); + } + else + { + + this.SetRes(this.left > right, $"{this.left} is not greater than {right}"); + } + } + + private SetRes(succeeded, failmsg) + { + if(!succeeded) + { + this.currentTest.success = false; + this.currentSpec.pass=false; + this.currentSpec.reason = failmsg; + } + } +} + +func Tesses.CrossLang.Shell.Test(dd) +{ + var offline=false; + var buildPath = "."; + var nobuild=false; + var allowFullCompTime=false; + var output=""; + var debug = false; + each(var flag : dd.Flags) + { + if(flag == "debug") + { + debug = true; + } + else if(flag == "offline") + { + offline = true; + } + else if(flag == "allow-insecure-comptime") + { + allowFullCompTime=true; + } + else if(flag == "help") + { + Console.WriteLine("USAGE: crosslang test [run-flags-and-options] program-arguments..."); + Console.WriteLine("USAGE: crosslang test [run-flags-and-options] -- program-arguments-and-options..."); + Console.WriteLine("FLAGS:"); + Console.WriteLine("offline: build with no internet (don't fetch files)"); + Console.WriteLine("allow-insecure-comptime: Allow full comptime"); + Console.WriteLine("nobuild: don't build, just run"); + Console.WriteLine("debug: emit line info into executable"); + Console.WriteLine("help: this help"); + Console.WriteLine(); + Console.WriteLine("OPTIONS:"); + Console.WriteLine("conf=CONFIGSTRING: specify a conf string for compile_tool(s), is the property Config"); + return 0; + } + else if(flag == "nobuild") + { + nobuild=true; + } + } + var conf = ""; + each(var option : dd.Options) + { + if(option.Key == "conf") + { + conf = option.Value; + } + } + if(nobuild) + { + if(FS.Local.FileExists("cross.json")) + { + var proj = Json.Decode(FS.ReadAllText(FS.Local, "cross.json")); + var nameVer = $"{proj.name}-{proj.version}.crvm"; + var buildDir = TypeOf(proj.bin_directory) == "String" ? proj.bin_directory : "bin"; + + output =./buildDir/nameVer; + if(!FS.Local.FileExists(output)) + { + Console.WriteLine($"ERROR: file {output} does not exist."); + } + } + else + { + Console.WriteLine("ERROR: could not find project."); + return 1; + } + } + else + { + var pm = new Tesses.CrossLang.PackageManager(); + pm.Offline = offline; + var bt = new Tesses.CrossLang.BuildTool(pm); + bt.Config = conf; + bt.Debug = debug; + bt.AllowFullCompTime = allowFullCompTime; + output = bt.BuildProject(buildPath).Output; + + } + + return Tesses.CrossLang.Shell.RunTestSuite(output); +} + + + +func Tesses.CrossLang.Shell.RunTestSuite(execFile) +{ + var configData = Json.Decode(FS.ReadAllText(FS.Local,"cross.json")) ?? {}; + var testDir = configData.test_directory ?? "tests"; + var sources = []; + func walk_for_source(sourceDir) + { + if(FS.Local.DirectoryExists(sourceDir)) + each(var file : FS.Local.EnumeratePaths(sourceDir)) + { + if(FS.Local.RegularFileExists(file) && file.GetExtension()==".tcross") + { + var src = { + FileName = file.ToString(), + Source = FS.ReadAllText(FS.Local, file) + }; + sources.Add(src); + } + else if(FS.Local.DirectoryExists(file)) + { + walk_for_source(file); + } + } + } + walk_for_source(testDir); + const strm = new MemoryStream(true); + var result = VM.Compile({ + Name = "/test/", + Version = "1.0.0.0-prod", + Sources = sources, + Dependencies = [], //they are loaded for us + Output = strm, + CompTime = compTimeEnv, + Debug = this.Debug + }); + if(result.Success) + { + const RED = "\e[0;91m"; + + const GREEN = "\e[0;92m"; + const YELLOW = "\e[;93m"; + const NC = "\e[0m"; + + strm.Seek(0,0); + + var vmFile = VM.LoadExecutable(strm); + strm.GetBytes().Resize(0); + + var env = VM.CreateEnvironment({}); + env.RegisterEverything(); + env.LockRegister(); + + env.LoadFileWithDependencies(FS.Local,execFile); + var failed = 0; + var total = 0; + var currentTest = null; + var currentSpec = null; + func Test(name, cb) + { + total++; + currentTest = { + success = true, + specs = [] + }; + testLists.Add(currentTest); + try { + cb(); + if(!currentTest.success) + { + failed++; + + Console.WriteLine($"[{RED}FAILED{NC}] {name}"); + each(var s : currentTest.specs) + { + if(s.todo) + { + Console.WriteLine($"\t[{YELLOW}TODO{NC}] {s.name}"); + } + else if(s.pass) + { + Console.WriteLine($"\t[{GREEN}PASS{NC}] {s.name}"); + } + else + { + Console.WriteLine($"\t[{RED}FAILED{NC}] {s.name}"); + Console.WriteLine($"\t\tReason: {s.reason}"); + } + } + } + else { + Console.WriteLine($"[{GREEN}PASS{NC}] {name}"); + each(var s : currentTest.specs) + { + if(s.todo) + { + Console.WriteLine($"\t[{YELLOW}TODO{NC}] {s.name}"); + } + else if(s.pass) + { + Console.WriteLine($"\t[{GREEN}PASS{NC}] {s.name}"); + } + } + } + + } catch(ex) + { + Console.WriteLine($"[{RED}EXCEPTION{NC}] {name}"); + Console.WriteLine($"\tReason: {ex}"); + each(var s : currentTest.specs) + { + if(s.todo) + { + Console.WriteLine($"\t[{YELLOW}TODO{NC}] {s.name}"); + } + else if(s.pass) + { + Console.WriteLine($"\t[{GREEN}PASS{NC}] {s.name}"); + } + else + { + Console.WriteLine($"\t[{RED}FAILED{NC}] {s.name}"); + Console.WriteLine($"\t\tReason: {s.reason}"); + } + } + failed++; + } + + currentTest = null; + } + func SpecToDo(name) + { + + if(TypeIsDictionary(currentSpec)) + { + throw "Must not be in Spec"; + } + if(TypeIsDictionary(currentTest)) + { + currentTest.specs.Add({ + todo = true, + name + }); + } + else { + throw "Must be within Test(\"My test name\",()=>{});"; + } + } + func Spec(name,cb) + { + if(TypeIsDictionary(currentSpec)) + { + throw "Must not be in Spec"; + } + if(TypeIsDictionary(currentTest)) + { + currentSpec = { + name + }; + currentTest.specs.Add(currentSpec); + + cb(); + + currentSpec.pass ??= true; + currentSpec = null; + + + + } else { + + throw "Must be within Test(\"My test name\",()=>{});"; + } + } + func Expect(val) + { + if(TypeIsDictionary(currentTest) && TypeIsDictionary(currentSpec)) + { + return new ExpectObj(currentTest,currentSpec,val); + } + else { + + throw "Must be within Spec(\"My spec name\",()=>{});"; + } + } + func Assert(val, failmsg) + { + if(TypeIsDictionary(currentTest) && TypeIsDictionary(currentSpec)) + { + if(val) + else + { + currentSpec.pass=false; + currentSpec.reason = failmsg; + currentTest.success=false; + } + + } + else { + + throw "Must be within Spec(\"My spec name\",()=>{});"; + } + } + env.SetVariable("Test", Test); + env.SetVariable("Spec", Spec); + env.SetVariable("SpecToDo", SpecToDo); + env.SetVariable("Expect", Expect); + env.SetVariable("Assert",Assert); + + const beginTime = DateTime.Now; + try { + env.LoadFile(vmFile); + + + } catch(ex) { + Console.WriteLine($"[{RED}EXCEPTION{NC}] {ex}"); + return 1; + } + + Console.WriteLine(); + if(failed == 0) + { + Console.WriteLine($"Tests {GREEN}passed{NC} in {(DateTime.Now-beginTime).ToString(true)}"); + } + else + { + + Console.WriteLine($"Tests {RED}failed{NC} in {(DateTime.Now-beginTime).ToString(true)}"); + } + Console.WriteLine($"Passed: {total-failed}"); + Console.WriteLine($"Failed: {failed}"); + Console.WriteLine($"Total: {total}"); + return failed > 0 ? 1 : 0; + } + return 0; +} \ No newline at end of file diff --git a/vscode-extension/src/api.ts b/vscode-extension/src/api.ts index 03e91f7..ebd36f8 100644 --- a/vscode-extension/src/api.ts +++ b/vscode-extension/src/api.ts @@ -202,6 +202,7 @@ export async function createTemplate() if(project_name) { const dir = join(getDocumentsFolder(),"CrossLangProjects",project_name); + mkdirSync(join(getDocumentsFolder(),"CrossLangProjects")); mkdirSync(dir); await readCommandToEnd("crosslang",["new",templateName,dir]); diff --git a/vscode-extension/syntaxes/crosslang.tmLanguage.json b/vscode-extension/syntaxes/crosslang.tmLanguage.json index e5489ea..47c6682 100644 --- a/vscode-extension/syntaxes/crosslang.tmLanguage.json +++ b/vscode-extension/syntaxes/crosslang.tmLanguage.json @@ -112,7 +112,7 @@ }, { "name": "keyword.control.crosslang", - "match": "\\b(if|else|while|for|do|return|each|in|break|try|catch|finally|defer|enumerable|yield|switch|case|default|await|breakpoint|throw)\\b" + "match": "\\b(if|else|while|for|do|return|each|in|break|continue|try|catch|finally|defer|enumerable|yield|switch|case|default|await|breakpoint|throw)\\b" }, { "name": "keyword.crosslang",