From 83f2c4d786b360fa2b9b57b7d942e015c0ad198f Mon Sep 17 00:00:00 2001 From: tritao Date: Thu, 17 Apr 2025 03:07:40 +0100 Subject: [PATCH 1/6] Implement support for properties in Emscripten generator. --- .github/workflows/main.yml | 2 +- src/Generator/Driver.cs | 14 +++++++-- .../Emscripten/EmscriptenSources.cs | 31 +++++++++++++++---- tests/Classes.h | 22 ++++++++++--- tests/emscripten/test.mjs | 9 +++++- tests/emscripten/test.sh | 4 +-- 6 files changed, 66 insertions(+), 16 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c98fea6f9..1dcd12119 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -37,7 +37,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup emsdk - uses: mymindstorm/setup-emsdk@v11 + uses: mymindstorm/setup-emsdk@v14 with: version: ${{ env.EMSCRIPTEN_VERSION }} actions-cache-folder: emsdk-cache-${{ runner.os }} diff --git a/src/Generator/Driver.cs b/src/Generator/Driver.cs index f9be9845b..cf20a268d 100644 --- a/src/Generator/Driver.cs +++ b/src/Generator/Driver.cs @@ -252,7 +252,12 @@ public void SetupPasses(ILibrary library) passes.AddPass(new CleanInvalidDeclNamesPass()); passes.AddPass(new FastDelegateToDelegatesPass()); - passes.AddPass(new FieldToPropertyPass()); + + if (Options.GeneratorKind != GeneratorKind.Emscripten) + { + passes.AddPass(new FieldToPropertyPass()); + } + passes.AddPass(new CheckIgnoredDeclsPass()); passes.AddPass(new CheckEnumsPass()); passes.AddPass(new MakeProtectedNestedTypesPublicPass()); @@ -283,9 +288,14 @@ public void SetupPasses(ILibrary library) passes.AddPass(new CheckDuplicatedNamesPass()); - if (Options.IsCLIGenerator || Options.IsCSharpGenerator) + if (Options.IsCLIGenerator || Options.IsCSharpGenerator + || Options.GeneratorKind.ID is GeneratorKind.Emscripten_ID) { passes.RenameDeclsUpperCase(RenameTargets.Any & ~RenameTargets.Parameter); + } + + if (Options.IsCLIGenerator || Options.IsCSharpGenerator) + { passes.AddPass(new CheckKeywordNamesPass()); } diff --git a/src/Generator/Generators/Emscripten/EmscriptenSources.cs b/src/Generator/Generators/Emscripten/EmscriptenSources.cs index 3918ef2a5..c1c99a1d9 100644 --- a/src/Generator/Generators/Emscripten/EmscriptenSources.cs +++ b/src/Generator/Generators/Emscripten/EmscriptenSources.cs @@ -117,11 +117,6 @@ public override void VisitClassConstructors(IEnumerable ctors) } } - public override bool VisitProperty(Property property) - { - return true; - } - public override bool VisitMethodDecl(Method method) { Indent(); @@ -130,9 +125,33 @@ public override bool VisitMethodDecl(Method method) return ret; } + public override bool VisitProperty(Property property) + { + if (property.Field != null) + return property.Field.Visit(this); + + if (!property.GetMethod.IsConst) + { + Console.WriteLine($"Cannot bind non-const property getter method: {property.GetMethod.QualifiedOriginalName}"); + return false; + } + + var @class = property.Namespace as Class; + Indent(); + Write($".property(\"{property.Name}\", &{@class.QualifiedOriginalName}::{property.GetMethod.OriginalName}"); + + if (property.HasSetter) + Write($", &{@class.QualifiedOriginalName}::{property.SetMethod.OriginalName}"); + + WriteLine(")"); + Unindent(); + + return true; + } + public override bool VisitFieldDecl(Field field) { - WriteLineIndent($".field(\"{field.Name}\", &{field.Class.QualifiedOriginalName}::{field.OriginalName})"); + WriteLineIndent($".property(\"{field.Name}\", &{field.Class.QualifiedOriginalName}::{field.OriginalName})"); return true; } diff --git a/tests/Classes.h b/tests/Classes.h index 7a43d8a11..718e12ce1 100644 --- a/tests/Classes.h +++ b/tests/Classes.h @@ -6,8 +6,8 @@ class Class { public: void ReturnsVoid() {} - int ReturnsInt() { return 0; } - // Class* PassAndReturnsClassPtr(Class* obj) { return obj; } + int ReturnsInt() const { return 0; } + Class* PassAndReturnsClassPtr(Class* obj) { return obj; } }; class ClassWithField @@ -18,7 +18,21 @@ class ClassWithField { } int Field; - int ReturnsField() { return Field; } + int ReturnsField() const { return Field; } +}; + +class ClassWithProperty +{ +public: + ClassWithProperty() + : Field(10) + { + } + int GetField() const { return Field; } + void SetField(int value) { Field = value; } + +private: + int Field; }; class ClassWithOverloads @@ -41,4 +55,4 @@ class ClassWithExternalInheritance : public ClassFromAnotherUnit }; // void FunctionPassClassByRef(Class* klass) { } -// Class* FunctionReturnsClassByRef() { return new Class(); } \ No newline at end of file +// Class* FunctionReturnsClassByRef() { return new Class(); } diff --git a/tests/emscripten/test.mjs b/tests/emscripten/test.mjs index fc65f55a5..f6098892f 100644 --- a/tests/emscripten/test.mjs +++ b/tests/emscripten/test.mjs @@ -111,7 +111,14 @@ function classes() { var classWithField = new test.ClassWithField(); eq(classWithField.ReturnsField(), 10); - //eq(classWithField.Field, 10); + eq(classWithField.Field, 10); + classWithField.Field = 20; + eq(classWithField.ReturnsField(), 20); + + var classWithProperty = new test.ClassWithProperty(); + eq(classWithProperty.Field, 10); + classWithProperty.Field = 20; + eq(classWithProperty.Field, 20); } builtins(); diff --git a/tests/emscripten/test.sh b/tests/emscripten/test.sh index d57a13df4..2475b43cb 100755 --- a/tests/emscripten/test.sh +++ b/tests/emscripten/test.sh @@ -38,8 +38,8 @@ generate=true if [ $generate = true ]; then echo "${green}Generating bindings${reset}" - dotnet $rootdir/bin/${dotnet_configuration}/CppSharp.CLI.dll \ - --gen=emscripten --platform=emscripten --arch=wasm32 \ + dotnet $rootdir/bin/${dotnet_configuration}_${platform}/CppSharp.CLI.dll \ + --gen=emscripten --platform=emscripten --arch=wasm32 --property=keywords \ -I$dir/.. -I$rootdir/include -o $dir/gen -m tests $dir/../*.h fi From 258129a8b0f51cdec2c7619c18e19e872b368b73 Mon Sep 17 00:00:00 2001 From: tritao Date: Fri, 18 Apr 2025 00:48:39 +0100 Subject: [PATCH 2/6] CLI: Fix option validation to check for valid Emscripten platform. --- src/CLI/Generator.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/CLI/Generator.cs b/src/CLI/Generator.cs index 7dfce75c5..917fe565b 100644 --- a/src/CLI/Generator.cs +++ b/src/CLI/Generator.cs @@ -90,6 +90,13 @@ public bool ValidateOptions(List messages) options.Platform ??= Platform.Host; + if (options.Architecture is TargetArchitecture.WASM32 or TargetArchitecture.WASM64 && + options.Platform is not TargetPlatform.Emscripten) + { + messages.Add("Please set Emscripten platform for WASM architectures."); + return false; + } + if (string.IsNullOrEmpty(options.OutputDir)) { options.OutputDir = Path.Combine(Directory.GetCurrentDirectory(), "gen"); From 0478498ebaf5009b757c20aea77e582897f993ae Mon Sep 17 00:00:00 2001 From: tritao Date: Fri, 18 Apr 2025 00:50:08 +0100 Subject: [PATCH 3/6] Ignore functions returning tag types for now in JS generation. --- src/Generator/Options.cs | 2 ++ src/Generator/Passes/CheckIgnoredDecls.cs | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/src/Generator/Options.cs b/src/Generator/Options.cs index 0d9a00084..1c078b474 100644 --- a/src/Generator/Options.cs +++ b/src/Generator/Options.cs @@ -205,6 +205,8 @@ public bool DoAllModulesHaveLibraries() => public bool IsCLIGenerator => GeneratorKind == GeneratorKind.CLI; + public bool IsJSGenerator => GeneratorKind == GeneratorKind.Emscripten || GeneratorKind == GeneratorKind.QuickJS; + public readonly List DependentNameSpaces = new List(); public bool MarshalCharAsManagedChar { get; set; } public bool MarshalConstCharArrayAsString { get; set; } = true; diff --git a/src/Generator/Passes/CheckIgnoredDecls.cs b/src/Generator/Passes/CheckIgnoredDecls.cs index 0ebf99f15..84721f208 100644 --- a/src/Generator/Passes/CheckIgnoredDecls.cs +++ b/src/Generator/Passes/CheckIgnoredDecls.cs @@ -184,6 +184,14 @@ public override bool VisitFunctionDecl(Function function) return false; } + if (Options.IsJSGenerator && function is Method { Kind: CXXMethodKind.Normal } && ret.Type.GetFinalPointee().IsClass()) + { + function.ExplicitlyIgnore(); + Diagnostics.Debug("Function '{0}' was ignored due to {1} return decl not yet implemented in JS generators", + function.Name, msg); + return false; + } + foreach (var param in function.Parameters) { if (HasInvalidDecl(param, out msg)) From 031a9006e40fe987118faa558930535b04d981cc Mon Sep 17 00:00:00 2001 From: tritao Date: Fri, 18 Apr 2025 00:57:48 +0100 Subject: [PATCH 4/6] Use a Lua bindings spec file for Emscripten tests. --- tests/emscripten/bindings.lua | 23 +++++++++++++++++++++++ tests/emscripten/test.sh | 5 ++--- 2 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 tests/emscripten/bindings.lua diff --git a/tests/emscripten/bindings.lua b/tests/emscripten/bindings.lua new file mode 100644 index 000000000..ee1cb53bc --- /dev/null +++ b/tests/emscripten/bindings.lua @@ -0,0 +1,23 @@ +generator "emscripten" +platform "emscripten" +architecture "wasm32" + +includedirs +{ + "..", + "../../include", +} + +output "gen" + +module "tests" + namespace "test" + headers + { + "Builtins.h", + "Classes.h", + "Classes2.h", + "Delegates.h", + "Enums.h", + "Overloads.h" + } diff --git a/tests/emscripten/test.sh b/tests/emscripten/test.sh index 2475b43cb..fac300702 100755 --- a/tests/emscripten/test.sh +++ b/tests/emscripten/test.sh @@ -38,9 +38,8 @@ generate=true if [ $generate = true ]; then echo "${green}Generating bindings${reset}" - dotnet $rootdir/bin/${dotnet_configuration}_${platform}/CppSharp.CLI.dll \ - --gen=emscripten --platform=emscripten --arch=wasm32 --property=keywords \ - -I$dir/.. -I$rootdir/include -o $dir/gen -m tests $dir/../*.h + dotnet $rootdir/bin/${dotnet_configuration}/CppSharp.CLI.dll --property=keywords \ + $dir/bindings.lua fi echo "${green}Building generated binding files${reset}" From 397f59bffdb3a3a2d907a589de733a740212fcec Mon Sep 17 00:00:00 2001 From: tritao Date: Fri, 18 Apr 2025 02:03:59 +0100 Subject: [PATCH 5/6] Improve JS testing scripts. --- .github/workflows/main.yml | 4 +- tests/emscripten/test.sh | 75 ++++++++++++++++++++++++-------------- tests/napi/test.sh | 64 +++++++++++++++++++++++++++----- tests/quickjs/test.sh | 75 +++++++++++++++++++++++++------------- tests/test.sh | 2 +- tests/ts/test.sh | 58 +++++++++++++++++++++-------- 6 files changed, 197 insertions(+), 81 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1dcd12119..019173bb8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -88,12 +88,12 @@ jobs: - name: Test (QuickJS) if: runner.os != 'Windows' shell: bash - run: tests/quickjs/test.sh -dotnet_configuration $BUILD_CONFIGURATION + run: tests/quickjs/test.sh --dotnet-config $BUILD_CONFIGURATION - name: Test (Emscripten) if: runner.os != 'Windows' shell: bash - run: tests/emscripten/test.sh -dotnet_configuration $BUILD_CONFIGURATION + run: tests/emscripten/test.sh --dotnet-config $BUILD_CONFIGURATION - name: Pack shell: bash diff --git a/tests/emscripten/test.sh b/tests/emscripten/test.sh index fac300702..11741c453 100755 --- a/tests/emscripten/test.sh +++ b/tests/emscripten/test.sh @@ -1,27 +1,46 @@ #!/usr/bin/env bash set -e + dir=$(cd "$(dirname "$0")"; pwd) rootdir="$dir/../.." -dotnet_configuration=Release -configuration=debug +dotnet_configuration=DebugOpt +make_configuration=debug platform=x64 jsinterp=$(which node) -for arg in "$@"; do - case $arg in - --with-node=*) - jsinterp="${arg#*=}" - shift - ;; - -configuration) - configuration=$2 - shift - ;; - -dotnet_configuration) - dotnet_configuration=$2 - shift - ;; - esac +usage() { + cat <&2 + usage + ;; + esac done if [ "$CI" = "true" ]; then @@ -34,19 +53,21 @@ else reset=`tput sgr0` fi +# 1) Generate generate=true - if [ $generate = true ]; then - echo "${green}Generating bindings${reset}" - dotnet $rootdir/bin/${dotnet_configuration}/CppSharp.CLI.dll --property=keywords \ - $dir/bindings.lua + echo "${green}Generating bindings with .NET configuration $dotnet_configuration${reset}" + dotnet "$rootdir/bin/${dotnet_configuration}/CppSharp.CLI.dll" --property=keywords \ + "$dir/bindings.lua" fi -echo "${green}Building generated binding files${reset}" -premake=$rootdir/build/premake.sh -config=$configuration $premake --file=$dir/premake5.lua gmake -emmake make -C $dir/gen -echo +# 2) Build +echo "${green}Building generated binding files (make config: $make_configuration)${reset}" +premake="$rootdir/build/premake.sh" +"$premake" --file=$dir/premake5.lua gmake2 +config=$make_configuration emmake make -C "$dir/gen" +# 3) Test +echo echo "${green}Executing JS tests with Node${reset}" -$jsinterp $dir/test.mjs \ No newline at end of file +"$jsinterp" "$dir/test.mjs" diff --git a/tests/napi/test.sh b/tests/napi/test.sh index 2f85fa34b..c2244913e 100755 --- a/tests/napi/test.sh +++ b/tests/napi/test.sh @@ -1,9 +1,47 @@ #!/usr/bin/env bash set -e + dir=$(cd "$(dirname "$0")"; pwd) rootdir="$dir/../.." -configuration=Release +dotnet_configuration=DebugOpt +make_configuration=debug platform=x64 +jsinterp=$(which node) + +usage() { + cat <&2 + usage + ;; + esac +done if [ "$CI" = "true" ]; then red="" @@ -15,15 +53,21 @@ else reset=`tput sgr0` fi -echo "${green}Generating bindings${reset}" -dotnet $rootdir/bin/${configuration}/CppSharp.CLI.dll \ - --gen=napi -I$dir/.. -o $dir/gen -m tests $dir/../*.h +# 1) Generate +generate=true +if [ $generate = true ]; then + echo "${green}Generating bindings with .NET configuration $dotnet_configuration${reset}" + dotnet "$rootdir/bin/${dotnet_configuration}/CppSharp.CLI.dll" \ + --gen=napi -I$dir/.. -o $dir/gen -m tests $dir/../*.h +fi -echo "${green}Building generated binding files${reset}" -premake=$rootdir/build/premake.sh -$premake --file=$dir/premake5.lua gmake -make -C $dir/gen -echo +# 2) Build +echo "${green}Building generated binding files (make config: $make_configuration)${reset}" +premake="$rootdir/build/premake.sh" +"$premake" --file=$dir/premake5.lua gmake +config=$make_configuration make -C "$dir/gen" +# 3) Test +echo echo "${green}Executing JS tests with Node${reset}" -node $dir/test.js +"$jsinterp" "$dir/test.js" diff --git a/tests/quickjs/test.sh b/tests/quickjs/test.sh index 102c72696..6326b0dd1 100755 --- a/tests/quickjs/test.sh +++ b/tests/quickjs/test.sh @@ -1,25 +1,47 @@ #!/usr/bin/env bash set -e + dir=$(cd "$(dirname "$0")"; pwd) rootdir="$dir/../.." -dotnet_configuration=Release -configuration=debug +dotnet_configuration=DebugOpt +make_configuration=debug +platform=x64 jsinterp="$dir/runtime/build/qjs" -for arg in "$@"; do - case $arg in - -configuration) - configuration=$2 - shift - ;; - -dotnet_configuration) - dotnet_configuration=$2 - shift - ;; - esac -done +usage() { + cat <&2 + usage + ;; + esac +done if [ "$CI" = "true" ]; then red="" @@ -31,20 +53,21 @@ else reset=`tput sgr0` fi +# 1) Generate generate=true - if [ $generate = true ]; then - echo "${green}Generating bindings${reset}" - dotnet $rootdir/bin/${dotnet_configuration}/CppSharp.CLI.dll \ - $dir/bindings.lua + echo "${green}Generating bindings with .NET configuration $dotnet_configuration${reset}" + dotnet "$rootdir/bin/${dotnet_configuration}/CppSharp.CLI.dll" "$dir/bindings.lua" fi -echo "${green}Building generated binding files${reset}" -premake=$rootdir/build/premake.sh -config=$configuration $premake --file=$dir/premake5.lua gmake2 -verbose=true make -C $dir/gen -echo +# 2) Build +echo "${green}Building generated binding files (make config: $make_configuration)${reset}" +premake="$rootdir/build/premake.sh" +"$premake" --file=$dir/premake5.lua gmake2 +config=$make_configuration verbose=true make -C "$dir/gen" +# 3) Test +echo echo "${green}Executing JS tests with QuickJS${reset}" -cp $dir/gen/bin/$configuration/libtest.so $dir -$jsinterp --std $dir/test.js \ No newline at end of file +cp $dir/gen/bin/$make_configuration/libtest.so $dir +$jsinterp --std $dir/test.js diff --git a/tests/test.sh b/tests/test.sh index f5abc712b..d9d645413 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -2,6 +2,6 @@ set -e dir=$(cd "$(dirname "$0")"; pwd) -$dir/napi/test.sh +# $dir/napi/test.sh $dir/quickjs/test.sh $dir/emscripten/test.sh diff --git a/tests/ts/test.sh b/tests/ts/test.sh index c4d39ba6a..a96e87589 100755 --- a/tests/ts/test.sh +++ b/tests/ts/test.sh @@ -1,11 +1,47 @@ #!/usr/bin/env bash set -e + dir=$(cd "$(dirname "$0")"; pwd) rootdir="$dir/../.." -dotnet_configuration=Release -configuration=debug +dotnet_configuration=DebugOpt +make_configuration=debug platform=x64 -jsinterp="$rootdir/deps/quickjs/qjs-debug" +jsinterp="$dir/runtime/build/qjs" + +usage() { + cat <&2 + usage + ;; + esac +done if [ "$CI" = "true" ]; then red="" @@ -17,22 +53,14 @@ else reset=`tput sgr0` fi +# 1) Generate generate=true - if [ $generate = true ]; then - echo "${green}Generating bindings${reset}" - dotnet $rootdir/bin/${dotnet_configuration}/CppSharp.CLI.dll \ + echo "${green}Generating bindings with .NET configuration $dotnet_configuration${reset}" + dotnet "$rootdir/bin/${dotnet_configuration}/CppSharp.CLI.dll" \ --gen=ts -I$dir/.. -I$rootdir/include -o $dir/gen -m tests $dir/../*.h fi -echo "${green}Building generated binding files${reset}" -#make -C $dir/gen -echo - +# 2) Type-checking echo "${green}Typechecking generated binding files with tsc${reset}" #tsc --noEmit --strict --noImplicitAny --strictNullChecks --strictFunctionTypes --noImplicitThis gen/*.d.ts - -# echo "${green}Executing JS tests with QuickJS${reset}" -# cp $dir/gen/bin/$configuration/libtest.so $dir -# #cp $dir/gen/bin/$configuration/libtest.dylib $dir -# $jsinterp --std $dir/test.js \ No newline at end of file From 5539a613499eac0d8458a9f20cd3889b00b9bbd4 Mon Sep 17 00:00:00 2001 From: tritao Date: Fri, 18 Apr 2025 02:50:20 +0100 Subject: [PATCH 6/6] Only run packing step for release CI builds. --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 019173bb8..08ae12054 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -96,6 +96,7 @@ jobs: run: tests/emscripten/test.sh --dotnet-config $BUILD_CONFIGURATION - name: Pack + if: matrix.build-cfg == 'Release' shell: bash run: build/build.sh prepack -platform $PLATFORM -configuration $BUILD_CONFIGURATION