diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..ed08e49 --- /dev/null +++ b/.clang-format @@ -0,0 +1,161 @@ +--- +Language: Cpp +# BasedOnStyle: Microsoft +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortEnumsOnASingleLine: false +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: true + AfterControlStatement: Always + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: false + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: true +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +StatementAttributeLikeMacros: + - Q_EMIT +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentCaseLabels: false +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: AfterExternBlock +IndentRequires: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 1000 +PenaltyIndentedWhitespace: 0 +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SortJavaStaticImport: Before +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceAroundPointerQualifiers: Default +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: Latest +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +... diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..554459d --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,596 @@ +--- +Checks: '*, + -bugprone-easily-swappable-parameters, + -bugprone-lambda-function-name, + -readability-magic-numbers, + -hicpp-no-array-decay, + -hicpp-signed-bitwise, + -hicpp-vararg, + -misc-non-private-member-variables-in-classes, + -cppcoreguidelines-*, + -fuchsia-*, + -altera-*, + -android-*, + -llvmlibc-*, + -readability-convert-member-functions-to-static, + -boost-use-ranges, + -google-objc-function-naming, + -google-objc-global-variable-declaration, + -performance-enum-size' +WarningsAsErrors: '' +HeaderFilterRegex: '^(?!magic_enum).*$' +FormatStyle: Microsoft +CheckOptions: + - key: readability-identifier-naming.ClassCase + value: CamelCase + - key: readability-identifier-naming.ClassMemberCase + value: lower_case + - key: readability-identifier-naming.ConstexprVariableCase + value: CamelCase + - key: readability-identifier-naming.ConstexprVariablePrefix + value: k + - key: readability-identifier-naming.EnumCase + value: CamelCase + - key: readability-identifier-naming.EnumConstantCase + value: CamelCase + - key: readability-identifier-naming.EnumConstantPrefix + value: k + - key: readability-identifier-naming.FunctionCase + value: camelBack + - key: readability-identifier-naming.FunctionIgnoredRegexp + value: '^BM_.*$' + - key: readability-identifier-naming.GlobalConstantCase + value: CamelCase + - key: readability-identifier-naming.GlobalConstantPrefix + value: k + - key: readability-identifier-naming.StaticConstantCase + value: CamelCase + - key: readability-identifier-naming.StaticConstantPrefix + value: k + - key: readability-identifier-naming.StaticVariableCase + value: lower_case + - key: readability-identifier-naming.MacroDefinitionCase + value: UPPER_CASE + - key: readability-identifier-naming.MacroDefinitionIgnoredRegexp + value: '^[A-Z]+(_[A-Z]+)*_$' + - key: readability-identifier-naming.MemberCase + value: lower_case + - key: readability-identifier-naming.PrivateMemberSuffix + value: _ + - key: readability-identifier-naming.PublicMemberSuffix + value: '' + - key: readability-identifier-naming.NamespaceCase + value: lower_case + - key: readability-identifier-naming.ParameterCase + value: lower_case + - key: readability-identifier-naming.TypeAliasCase + value: CamelCase + - key: readability-identifier-naming.TypedefCase + value: CamelCase + - key: readability-identifier-naming.VariableCase + value: lower_case + - key: readability-identifier-naming.IgnoreMainLikeFunctions + value: 'true' + - key: readability-identifier-naming.AggressiveDependentMemberLookup + value: 'false' + - key: readability-identifier-naming.IgnoreFailedSplit + value: 'false' + - key: abseil-string-find-startswith.AbseilStringsMatchHeader + value: 'absl/strings/match.h' + - key: abseil-string-find-startswith.IncludeStyle + value: llvm + - key: abseil-string-find-startswith.StringLikeClasses + value: '::std::basic_string' + - key: abseil-string-find-str-contains.AbseilStringsMatchHeader + value: 'absl/strings/match.h' + - key: abseil-string-find-str-contains.IncludeStyle + value: llvm + - key: abseil-string-find-str-contains.StringLikeClasses + value: '::std::basic_string;::std::basic_string_view;::absl::string_view' + - key: bugprone-argument-comment.CommentBoolLiterals + value: '0' + - key: bugprone-argument-comment.CommentCharacterLiterals + value: '0' + - key: bugprone-argument-comment.CommentFloatLiterals + value: '0' + - key: bugprone-argument-comment.CommentIntegerLiterals + value: '0' + - key: bugprone-argument-comment.CommentNullPtrs + value: '0' + - key: bugprone-argument-comment.CommentStringLiterals + value: '0' + - key: bugprone-argument-comment.CommentUserDefinedLiterals + value: '0' + - key: bugprone-argument-comment.IgnoreSingleArgument + value: '0' + - key: bugprone-argument-comment.StrictMode + value: '0' + - key: bugprone-assert-side-effect.AssertMacros + value: assert + - key: bugprone-assert-side-effect.CheckFunctionCalls + value: 'false' + - key: bugprone-dangling-handle.HandleClasses + value: 'std::basic_string_view;std::experimental::basic_string_view' + - key: bugprone-dynamic-static-initializers.HeaderFileExtensions + value: ';h;hh;hpp;hxx' + - key: bugprone-exception-escape.FunctionsThatShouldNotThrow + value: '' + - key: bugprone-exception-escape.IgnoredExceptions + value: '' + - key: bugprone-misplaced-widening-cast.CheckImplicitCasts + value: 'false' + - key: bugprone-narrowing-conversions.PedanticMode + value: 'false' + - key: bugprone-narrowing-conversions.WarnOnFloatingPointNarrowingConversion + value: 'true' + - key: bugprone-not-null-terminated-result.WantToUseSafeFunctions + value: 'true' + - key: bugprone-reserved-identifier.AggressiveDependentMemberLookup + value: 'false' + - key: bugprone-reserved-identifier.AllowedIdentifiers + value: '' + - key: bugprone-reserved-identifier.Invert + value: 'false' + - key: bugprone-signed-char-misuse.CharTypdefsToIgnore + value: '' + - key: bugprone-signed-char-misuse.DiagnoseSignedUnsignedCharComparisons + value: 'true' + - key: bugprone-sizeof-expression.WarnOnSizeOfCompareToConstant + value: 'true' + - key: bugprone-sizeof-expression.WarnOnSizeOfConstant + value: 'true' + - key: bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression + value: 'false' + - key: bugprone-sizeof-expression.WarnOnSizeOfThis + value: 'true' + - key: bugprone-string-constructor.LargeLengthThreshold + value: '8388608' + - key: bugprone-string-constructor.WarnOnLargeLength + value: 'true' + - key: bugprone-suspicious-enum-usage.StrictMode + value: 'false' + - key: bugprone-suspicious-include.HeaderFileExtensions + value: ';h;hh;hpp;hxx' + - key: bugprone-suspicious-include.ImplementationFileExtensions + value: 'c;cc;cpp;cxx' + - key: bugprone-suspicious-missing-comma.MaxConcatenatedTokens + value: '5' + - key: bugprone-suspicious-missing-comma.RatioThreshold + value: '0.200000' + - key: bugprone-suspicious-missing-comma.SizeThreshold + value: '5' + - key: bugprone-suspicious-string-compare.StringCompareLikeFunctions + value: '' + - key: bugprone-suspicious-string-compare.WarnOnImplicitComparison + value: 'true' + - key: bugprone-suspicious-string-compare.WarnOnLogicalNotComparison + value: 'false' + - key: bugprone-too-small-loop-variable.MagnitudeBitsUpperLimit + value: '16' + - key: bugprone-unhandled-self-assignment.WarnOnlyIfThisHasSuspiciousField + value: 'true' + - key: bugprone-unused-return-value.CheckedFunctions + value: '::std::async;::std::launder;::std::remove;::std::remove_if;::std::unique;::std::unique_ptr::release;::std::basic_string::empty;::std::vector::empty;::std::back_inserter;::std::distance;::std::find;::std::find_if;::std::inserter;::std::lower_bound;::std::make_pair;::std::map::count;::std::map::find;::std::map::lower_bound;::std::multimap::equal_range;::std::multimap::upper_bound;::std::set::count;::std::set::find;::std::setfill;::std::setprecision;::std::setw;::std::upper_bound;::std::vector::at;::bsearch;::ferror;::feof;::isalnum;::isalpha;::isblank;::iscntrl;::isdigit;::isgraph;::islower;::isprint;::ispunct;::isspace;::isupper;::iswalnum;::iswprint;::iswspace;::isxdigit;::memchr;::memcmp;::strcmp;::strcoll;::strncmp;::strpbrk;::strrchr;::strspn;::strstr;::wcscmp;::access;::bind;::connect;::difftime;::dlsym;::fnmatch;::getaddrinfo;::getopt;::htonl;::htons;::iconv_open;::inet_addr;::isascii;::isatty;::mmap;::newlocale;::openat;::pathconf;::pthread_equal;::pthread_getspecific;::pthread_mutex_trylock;::readdir;::readlink;::recvmsg;::regexec;::scandir;::semget;::setjmp;::shm_open;::shmget;::sigismember;::strcasecmp;::strsignal;::ttyname' + - key: cert-dcl16-c.IgnoreMacros + value: 'true' + - key: cert-dcl16-c.NewSuffixes + value: 'L;LL;LU;LLU' + - key: cert-dcl37-c.AggressiveDependentMemberLookup + value: 'false' + - key: cert-dcl37-c.AllowedIdentifiers + value: '' + - key: cert-dcl37-c.Invert + value: 'false' + - key: cert-dcl51-cpp.AggressiveDependentMemberLookup + value: 'false' + - key: cert-dcl51-cpp.AllowedIdentifiers + value: '' + - key: cert-dcl51-cpp.Invert + value: 'false' + - key: cert-dcl59-cpp.HeaderFileExtensions + value: ';h;hh;hpp;hxx' + - key: cert-err09-cpp.CheckThrowTemporaries + value: 'true' + - key: cert-err09-cpp.MaxSize + value: '1000000' + - key: cert-err09-cpp.WarnOnLargeObjects + value: 'false' + - key: cert-err61-cpp.CheckThrowTemporaries + value: 'true' + - key: cert-err61-cpp.MaxSize + value: '1000000' + - key: cert-err61-cpp.WarnOnLargeObjects + value: 'false' + - key: cert-msc32-c.DisallowedSeedTypes + value: 'time_t,std::time_t' + - key: cert-msc51-cpp.DisallowedSeedTypes + value: 'time_t,std::time_t' + - key: cert-oop11-cpp.IncludeStyle + value: llvm + - key: cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField + value: 'false' + - key: cert-oop57-cpp.MemCmpNames + value: '' + - key: cert-oop57-cpp.MemCpyNames + value: '' + - key: cert-oop57-cpp.MemSetNames + value: '' + - key: cert-str34-c.CharTypdefsToIgnore + value: '' + - key: cert-str34-c.DiagnoseSignedUnsignedCharComparisons + value: 'false' + - key: cppcoreguidelines-avoid-magic-numbers.IgnoreAllFloatingPointValues + value: 'false' + - key: cppcoreguidelines-avoid-magic-numbers.IgnoreBitFieldsWidths + value: 'true' + - key: cppcoreguidelines-avoid-magic-numbers.IgnorePowersOf2IntegerValues + value: 'false' + - key: cppcoreguidelines-avoid-magic-numbers.IgnoredFloatingPointValues + value: '1.0;100.0;' + - key: cppcoreguidelines-avoid-magic-numbers.IgnoredIntegerValues + value: '1;2;3;4;' + - key: cppcoreguidelines-explicit-virtual-functions.AllowOverrideAndFinal + value: 'false' + - key: cppcoreguidelines-explicit-virtual-functions.FinalSpelling + value: final + - key: cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors + value: 'true' + - key: cppcoreguidelines-explicit-virtual-functions.OverrideSpelling + value: override + - key: cppcoreguidelines-init-variables.IncludeStyle + value: llvm + - key: cppcoreguidelines-init-variables.MathHeader + value: math.h + - key: cppcoreguidelines-macro-usage.AllowedRegexp + value: '^DEBUG_*' + - key: cppcoreguidelines-macro-usage.CheckCapsOnly + value: 'false' + - key: cppcoreguidelines-macro-usage.IgnoreCommandLineMacros + value: 'true' + - key: cppcoreguidelines-narrowing-conversions.PedanticMode + value: 'false' + - key: cppcoreguidelines-narrowing-conversions.WarnOnFloatingPointNarrowingConversion + value: 'true' + - key: cppcoreguidelines-no-malloc.Allocations + value: '::malloc;::calloc' + - key: cppcoreguidelines-no-malloc.Deallocations + value: '::free' + - key: cppcoreguidelines-no-malloc.Reallocations + value: '::realloc' + - key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic + value: 'true' + - key: cppcoreguidelines-non-private-member-variables-in-classes.IgnorePublicMemberVariables + value: 'false' + - key: cppcoreguidelines-owning-memory.LegacyResourceConsumers + value: '::free;::realloc;::freopen;::fclose' + - key: cppcoreguidelines-owning-memory.LegacyResourceProducers + value: '::malloc;::aligned_alloc;::realloc;::calloc;::fopen;::freopen;::tmpfile' + - key: cppcoreguidelines-pro-bounds-constant-array-index.GslHeader + value: '' + - key: cppcoreguidelines-pro-bounds-constant-array-index.IncludeStyle + value: llvm + - key: cppcoreguidelines-pro-type-member-init.IgnoreArrays + value: 'false' + - key: cppcoreguidelines-pro-type-member-init.UseAssignment + value: 'false' + - key: cppcoreguidelines-special-member-functions.AllowMissingMoveFunctions + value: 'false' + - key: cppcoreguidelines-special-member-functions.AllowMissingMoveFunctionsWhenCopyIsDeleted + value: 'false' + - key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor + value: 'false' + - key: google-build-namespaces.HeaderFileExtensions + value: ';h;hh;hpp;hxx' + - key: google-global-names-in-headers.HeaderFileExtensions + value: ';h;hh;hpp;hxx' + - key: google-readability-braces-around-statements.ShortStatementLines + value: '1' + - key: google-readability-function-size.BranchThreshold + value: '4294967295' + - key: google-readability-function-size.LineThreshold + value: '4294967295' + - key: google-readability-function-size.NestingThreshold + value: '4294967295' + - key: google-readability-function-size.ParameterThreshold + value: '4294967295' + - key: google-readability-function-size.StatementThreshold + value: '800' + - key: google-readability-function-size.VariableThreshold + value: '4294967295' + - key: google-readability-namespace-comments.ShortNamespaceLines + value: '10' + - key: google-readability-namespace-comments.SpacesBeforeComments + value: '2' + - key: google-runtime-int.SignedTypePrefix + value: int + - key: google-runtime-int.TypeSuffix + value: '' + - key: google-runtime-int.UnsignedTypePrefix + value: uint + - key: google-runtime-references.IncludedTypes + value: '' + - key: hicpp-braces-around-statements.ShortStatementLines + value: '0' + - key: hicpp-function-size.BranchThreshold + value: '4294967295' + - key: hicpp-function-size.LineThreshold + value: '4294967295' + - key: hicpp-function-size.NestingThreshold + value: '4294967295' + - key: hicpp-function-size.ParameterThreshold + value: '4294967295' + - key: hicpp-function-size.StatementThreshold + value: '800' + - key: hicpp-function-size.VariableThreshold + value: '4294967295' + - key: hicpp-member-init.IgnoreArrays + value: 'false' + - key: hicpp-member-init.UseAssignment + value: 'false' + - key: hicpp-move-const-arg.CheckTriviallyCopyableMove + value: 'true' + - key: hicpp-multiway-paths-covered.WarnOnMissingElse + value: 'false' + - key: hicpp-no-malloc.Allocations + value: '::malloc;::calloc' + - key: hicpp-no-malloc.Deallocations + value: '::free' + - key: hicpp-no-malloc.Reallocations + value: '::realloc' + - key: hicpp-signed-bitwise.IgnorePositiveIntegerLiterals + value: 'false' + - key: hicpp-special-member-functions.AllowMissingMoveFunctions + value: 'false' + - key: hicpp-special-member-functions.AllowMissingMoveFunctionsWhenCopyIsDeleted + value: 'false' + - key: hicpp-special-member-functions.AllowSoleDefaultDtor + value: 'false' + - key: hicpp-uppercase-literal-suffix.IgnoreMacros + value: 'true' + - key: hicpp-uppercase-literal-suffix.NewSuffixes + value: '' + - key: hicpp-use-auto.MinTypeNameLength + value: '5' + - key: hicpp-use-auto.RemoveStars + value: 'false' + - key: hicpp-use-emplace.ContainersWithPushBack + value: '::std::vector;::std::list;::std::deque' + - key: hicpp-use-emplace.IgnoreImplicitConstructors + value: 'false' + - key: hicpp-use-emplace.SmartPointers + value: '::std::shared_ptr;::std::unique_ptr;::std::auto_ptr;::std::weak_ptr' + - key: hicpp-use-emplace.TupleMakeFunctions + value: '::std::make_pair;::std::make_tuple' + - key: hicpp-use-emplace.TupleTypes + value: '::std::pair;::std::tuple' + - key: hicpp-use-equals-default.IgnoreMacros + value: 'true' + - key: hicpp-use-equals-delete.IgnoreMacros + value: 'true' + - key: hicpp-use-noexcept.ReplacementString + value: '' + - key: hicpp-use-noexcept.UseNoexceptFalse + value: 'true' + - key: hicpp-use-nullptr.NullMacros + value: '' + - key: hicpp-use-override.AllowOverrideAndFinal + value: 'false' + - key: hicpp-use-override.FinalSpelling + value: final + - key: hicpp-use-override.IgnoreDestructors + value: 'false' + - key: hicpp-use-override.OverrideSpelling + value: override + - key: llvm-else-after-return.WarnOnConditionVariables + value: 'false' + - key: llvm-else-after-return.WarnOnUnfixable + value: 'false' + - key: llvm-header-guard.HeaderFileExtensions + value: ';h;hh;hpp;hxx' + - key: llvm-namespace-comment.ShortNamespaceLines + value: '1' + - key: llvm-namespace-comment.SpacesBeforeComments + value: '1' + - key: llvm-qualified-auto.AddConstToQualified + value: 'false' + - key: misc-definitions-in-headers.HeaderFileExtensions + value: ';h;hh;hpp;hxx' + - key: misc-definitions-in-headers.UseHeaderFileExtension + value: 'true' + - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic + value: 'false' + - key: misc-non-private-member-variables-in-classes.IgnorePublicMemberVariables + value: 'false' + - key: misc-throw-by-value-catch-by-reference.CheckThrowTemporaries + value: 'true' + - key: misc-throw-by-value-catch-by-reference.MaxSize + value: '1000000' + - key: misc-throw-by-value-catch-by-reference.WarnOnLargeObjects + value: 'false' + - key: misc-unused-parameters.StrictMode + value: 'false' + - key: modernize-avoid-bind.PermissiveParameterList + value: 'false' + - key: modernize-loop-convert.MaxCopySize + value: '16' + - key: modernize-loop-convert.MinConfidence + value: reasonable + - key: modernize-loop-convert.NamingStyle + value: CamelCase + - key: modernize-make-shared.IgnoreMacros + value: 'true' + - key: modernize-make-shared.IncludeStyle + value: llvm + - key: modernize-make-shared.MakeSmartPtrFunction + value: 'std::make_shared' + - key: modernize-make-shared.MakeSmartPtrFunctionHeader + value: memory + - key: modernize-make-unique.IgnoreMacros + value: 'true' + - key: modernize-make-unique.IncludeStyle + value: llvm + - key: modernize-make-unique.MakeSmartPtrFunction + value: 'std::make_unique' + - key: modernize-make-unique.MakeSmartPtrFunctionHeader + value: memory + - key: modernize-pass-by-value.IncludeStyle + value: llvm + - key: modernize-pass-by-value.ValuesOnly + value: 'false' + - key: modernize-raw-string-literal.DelimiterStem + value: lit + - key: modernize-raw-string-literal.ReplaceShorterLiterals + value: 'false' + - key: modernize-replace-auto-ptr.IncludeStyle + value: llvm + - key: modernize-replace-disallow-copy-and-assign-macro.MacroName + value: DISALLOW_COPY_AND_ASSIGN + - key: modernize-replace-random-shuffle.IncludeStyle + value: llvm + - key: modernize-use-auto.MinTypeNameLength + value: '5' + - key: modernize-use-auto.RemoveStars + value: 'false' + - key: modernize-use-bool-literals.IgnoreMacros + value: 'true' + - key: modernize-use-default-member-init.IgnoreMacros + value: 'true' + - key: modernize-use-default-member-init.UseAssignment + value: 'false' + - key: modernize-use-emplace.ContainersWithPushBack + value: '::std::vector;::std::list;::std::deque' + - key: modernize-use-emplace.IgnoreImplicitConstructors + value: 'false' + - key: modernize-use-emplace.SmartPointers + value: '::std::shared_ptr;::std::unique_ptr;::std::auto_ptr;::std::weak_ptr' + - key: modernize-use-emplace.TupleMakeFunctions + value: '::std::make_pair;::std::make_tuple' + - key: modernize-use-emplace.TupleTypes + value: '::std::pair;::std::tuple' + - key: modernize-use-equals-default.IgnoreMacros + value: 'true' + - key: modernize-use-equals-delete.IgnoreMacros + value: 'true' + - key: modernize-use-nodiscard.ReplacementString + value: '[[nodiscard]]' + - key: modernize-use-noexcept.ReplacementString + value: '' + - key: modernize-use-noexcept.UseNoexceptFalse + value: 'true' + - key: modernize-use-nullptr.NullMacros + value: 'NULL' + - key: modernize-use-override.AllowOverrideAndFinal + value: 'false' + - key: modernize-use-override.FinalSpelling + value: final + - key: modernize-use-override.IgnoreDestructors + value: 'false' + - key: modernize-use-override.OverrideSpelling + value: override + - key: modernize-use-transparent-functors.SafeMode + value: 'false' + - key: modernize-use-using.IgnoreMacros + value: 'true' + - key: objc-forbidden-subclassing.ForbiddenSuperClassNames + value: 'ABNewPersonViewController;ABPeoplePickerNavigationController;ABPersonViewController;ABUnknownPersonViewController;NSHashTable;NSMapTable;NSPointerArray;NSPointerFunctions;NSTimer;UIActionSheet;UIAlertView;UIImagePickerController;UITextInputMode;UIWebView' + - key: openmp-exception-escape.IgnoredExceptions + value: '' + - key: performance-faster-string-find.StringLikeClasses + value: '::std::basic_string;::std::basic_string_view' + - key: performance-for-range-copy.AllowedTypes + value: '' + - key: performance-for-range-copy.WarnOnAllAutoCopies + value: 'false' + - key: performance-inefficient-string-concatenation.StrictMode + value: 'false' + - key: performance-inefficient-vector-operation.EnableProto + value: 'false' + - key: performance-inefficient-vector-operation.VectorLikeClasses + value: '::std::vector' + - key: performance-move-const-arg.CheckTriviallyCopyableMove + value: 'true' + - key: performance-move-constructor-init.IncludeStyle + value: llvm + - key: performance-no-automatic-move.AllowedTypes + value: '' + - key: performance-type-promotion-in-math-fn.IncludeStyle + value: llvm + - key: performance-unnecessary-copy-initialization.AllowedTypes + value: '' + - key: performance-unnecessary-value-param.AllowedTypes + value: '' + - key: performance-unnecessary-value-param.IncludeStyle + value: llvm + - key: portability-restrict-system-includes.Includes + value: '*' + - key: portability-simd-intrinsics.Std + value: '' + - key: portability-simd-intrinsics.Suggest + value: '0' + - key: readability-braces-around-statements.ShortStatementLines + value: '0' + - key: readability-else-after-return.WarnOnConditionVariables + value: 'true' + - key: readability-else-after-return.WarnOnUnfixable + value: 'true' + - key: readability-function-size.BranchThreshold + value: '4294967295' + - key: readability-function-size.LineThreshold + value: '4294967295' + - key: readability-function-size.NestingThreshold + value: '4294967295' + - key: readability-function-size.ParameterThreshold + value: '4294967295' + - key: readability-function-size.StatementThreshold + value: '800' + - key: readability-function-size.VariableThreshold + value: '4294967295' + - key: readability-implicit-bool-conversion.AllowIntegerConditions + value: 'false' + - key: readability-implicit-bool-conversion.AllowPointerConditions + value: 'false' + - key: readability-inconsistent-declaration-parameter-name.IgnoreMacros + value: 'true' + - key: readability-inconsistent-declaration-parameter-name.Strict + value: 'false' + - key: readability-magic-numbers.IgnoreAllFloatingPointValues + value: 'false' + - key: readability-magic-numbers.IgnoreBitFieldsWidths + value: 'true' + - key: readability-magic-numbers.IgnorePowersOf2IntegerValues + value: 'false' + - key: readability-magic-numbers.IgnoredFloatingPointValues + value: '1.0;100.0;' + - key: readability-magic-numbers.IgnoredIntegerValues + value: '1;2;3;4;' + - key: readability-qualified-auto.AddConstToQualified + value: 'true' + - key: readability-redundant-declaration.IgnoreMacros + value: 'true' + - key: readability-redundant-member-init.IgnoreBaseInCopyConstructors + value: 'false' + - key: readability-redundant-smartptr-get.IgnoreMacros + value: 'true' + - key: readability-redundant-string-init.StringNames + value: '::std::basic_string' + - key: readability-simplify-boolean-expr.ChainedConditionalAssignment + value: 'false' + - key: readability-simplify-boolean-expr.ChainedConditionalReturn + value: 'false' + - key: readability-simplify-subscript-expr.Types + value: '::std::basic_string;::std::basic_string_view;::std::vector;::std::array' + - key: readability-static-accessed-through-instance.NameSpecifierNestingThreshold + value: '3' + - key: readability-uppercase-literal-suffix.IgnoreMacros + value: 'true' + - key: readability-uppercase-literal-suffix.NewSuffixes + value: '' + - key: zircon-temporary-objects.Names + value: '' +# allow x,y,z single letter names, to be used for coordinates +# allow fd (file descriptor) as it's a common POSIX convention + - key: readability-identifier-length.IgnoredVariableNames + value: '^(x|y|z|m0|m1|fd|_)$' + - key: readability-identifier-length.IgnoredParameterNames + value: '^(x|y|z|m0|m1|fd|_)$' +... diff --git a/.github/workflows/build-deno-so.yml b/.github/workflows/build-deno-so.yml new file mode 100644 index 0000000..7523a80 --- /dev/null +++ b/.github/workflows/build-deno-so.yml @@ -0,0 +1,55 @@ +name: Build Deno Shared Library + +on: + push: + branches: [ main, master ] + pull_request: + +jobs: + build-deno-so: + name: Build shared library for Deno + runs-on: ubuntu-latest + + container: + image: ubuntu:24.04 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install build dependencies + env: + DEBIAN_FRONTEND: noninteractive + run: | + apt-get update + apt-get install -y --no-install-recommends ninja-build g++ git ca-certificates + rm -rf /var/lib/apt/lists/* + + - name: Setup CMake >= 3.30 + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: '3.31.x' + + - name: Configure CMake (Release) + env: + CXX: g++ + run: | + cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release + + - name: Build shared library + run: | + cmake --build build --config Release + + - name: Collect shared library artifact + run: | + mkdir -p deno-lib/linux-x86_64 + cp build/libcpp_bindings_linux.so* deno-lib/linux-x86_64/ + ls -R deno-lib + + - name: Upload .so artifact for Deno + uses: actions/upload-artifact@v4 + with: + name: cpp-bindings-linux-so-linux-x86_64 + path: deno-lib/** + + diff --git a/.github/workflows/cpp-tests.yml b/.github/workflows/cpp-tests.yml new file mode 100644 index 0000000..cefe01e --- /dev/null +++ b/.github/workflows/cpp-tests.yml @@ -0,0 +1,52 @@ +name: C++ Tests + +on: + push: + branches: [ main, master ] + pull_request: + +jobs: + cpp-tests: + name: C++ tests (gcc) + runs-on: ubuntu-latest + + container: + image: fedora:43 + + env: + SERIAL_TEST_PORT: /tmp/ttyCI_A + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install build dependencies (gcc) + run: | + dnf -y update + dnf -y install cmake ninja-build gcc-c++ socat git + + - name: Configure CMake + env: + CXX: g++ + run: | + cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release + + - name: Build + run: | + cmake --build build --config Release + + - name: Start virtual serial echo (socat) + run: | + socat -d -d pty,raw,echo=0,link=/tmp/ttyCI_A,mode=666 pty,raw,echo=0,link=/tmp/ttyCI_B,mode=666 & + sleep 2 + stdbuf -i0 -o0 cat < /tmp/ttyCI_B > /tmp/ttyCI_B & + sleep 1 + + - name: Run C++ unit/integration tests + working-directory: build + env: + SERIAL_TEST_PORT: /tmp/ttyCI_A + run: | + ./cpp_bindings_linux_tests --gtest_color=yes + + diff --git a/.github/workflows/deno-tests.yml b/.github/workflows/deno-tests.yml new file mode 100644 index 0000000..983701b --- /dev/null +++ b/.github/workflows/deno-tests.yml @@ -0,0 +1,53 @@ +name: Deno Integration Tests + +on: + push: + branches: [ main, master ] + pull_request: + +jobs: + deno-tests: + name: Deno integration tests (deno ${{ matrix.deno }}) + runs-on: ubuntu-latest + + strategy: + matrix: + deno: ["2.6.0", "2.5.0"] + + container: + image: denoland/deno:${{ matrix.deno }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install build dependencies + run: | + apt-get update + apt-get install -y cmake ninja-build g++ socat git + + - name: Configure CMake + env: + CXX: g++ + run: | + cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release + + - name: Build + run: | + cmake --build build --config Release + + - name: Start virtual serial echo (socat) + run: | + socat -d -d pty,raw,echo=0,link=/tmp/ttyCI_A,mode=666 pty,raw,echo=0,link=/tmp/ttyCI_B,mode=666 & + sleep 2 + stdbuf -i0 -o0 cat < /tmp/ttyCI_B > /tmp/ttyCI_B & + sleep 1 + + - name: Run Deno integration tests + working-directory: integration_tests + env: + SERIAL_TEST_PORT: /tmp/ttyCI_A + run: | + deno task test + + diff --git a/.github/workflows/publish-jsr.yml b/.github/workflows/publish-jsr.yml new file mode 100644 index 0000000..63df8cc --- /dev/null +++ b/.github/workflows/publish-jsr.yml @@ -0,0 +1,66 @@ +name: Publish to JSR (@serial/cpp-bindings-linux) + +on: + workflow_dispatch: + inputs: {} + push: + tags: + - "v*" + +permissions: + contents: read + id-token: write + +jobs: + publish-jsr: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install build dependencies + env: + DEBIAN_FRONTEND: noninteractive + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends ninja-build g++ git ca-certificates + + - name: Setup CMake >= 3.30 + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: "3.31.x" + + - name: Setup Deno + uses: denoland/setup-deno@v2 + with: + deno-version: "2.6.0" + + - name: Configure CMake (Release) + env: + CXX: g++ + run: | + cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release + + - name: Build shared library + run: | + cmake --build build --config Release + + - name: Embed .so as JSON/base64 for JSR + run: | + deno run --allow-read --allow-write jsr/scripts/embed_so.ts \ + build/libcpp_bindings_linux.so \ + jsr/bin/x84_64.json \ + linux-x86_64 + + - name: Publish package to JSR + working-directory: jsr + run: | + # CMake + embed_so.ts generate files (jsr/jsr.json + binaries/*.json), + # which makes the worktree dirty. Publishing is still deterministic because + # the workflow builds + generates in a single run. + deno publish --allow-dirty + + diff --git a/CMakeLists.txt b/CMakeLists.txt index 2fdf9a9..292fb97 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ include(cmake/CPM.cmake) CPMAddPackage( NAME cmake_git_versioning GITHUB_REPOSITORY Katze719/cmake-git-versioning - GIT_TAG v1.0.0 + GIT_TAG v1.0.1 ) # Include cmake-git-versioning module @@ -23,6 +23,14 @@ project( LANGUAGES CXX ) +# Generate JSR package metadata from the same git-derived version as the library. +# We generate into the build directory to avoid touching tracked files during normal local builds. +configure_file( + "${CMAKE_SOURCE_DIR}/jsr/jsr.json.in" + "${CMAKE_SOURCE_DIR}/jsr/jsr.json" + @ONLY +) + # Set C++ standard set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -86,9 +94,6 @@ target_link_libraries( target_compile_features(cpp_bindings_linux PUBLIC cxx_std_23) - -enable_testing() - # Collect all test source files file( GLOB_RECURSE TEST_SOURCES @@ -115,9 +120,6 @@ if(TEST_SOURCES) ) target_compile_features(cpp_bindings_linux_tests PRIVATE cxx_std_23) - - include(GoogleTest) - gtest_discover_tests(cpp_bindings_linux_tests) endif() include(GNUInstallDirs) @@ -142,3 +144,15 @@ install( DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} OPTIONAL ) + +if(CMAKE_EXPORT_COMPILE_COMMANDS AND EXISTS "${CMAKE_BINARY_DIR}/compile_commands.json") + add_custom_target( + copy-compile-commands + ALL + ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_BINARY_DIR}/compile_commands.json + ${CMAKE_SOURCE_DIR}/compile_commands.json + DEPENDS ${CMAKE_BINARY_DIR}/compile_commands.json + COMMENT "Copying compile_commands.json to project root" + ) +endif() diff --git a/LICENSE b/LICENSE index 87e7774..fd8cd28 100644 --- a/LICENSE +++ b/LICENSE @@ -1,339 +1,298 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version of + the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is necessary + to install and execute a modified version of the Combined Work + produced by recombining or relinking the Application with a modified + version of the Linked Version. (If you use option 4d0, the + Installation Information must accompany the Minimal Corresponding + Source and Corresponding Application Code. If you use option 4d1, you + must provide the Installation Information in the manner specified by + section 6 of the GNU GPL for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + + 7. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 8. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or run +a copy of the Library. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + 11. Patents. + + A contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange to extend the +patent license to downstream recipients. "Knowingly relying" means you +have actual knowledge that, but for the patent license, your conveying +the covered work in a country, or your recipient's use of the covered +work in a country, would infringe one or more identifiable patents in +that country. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + If you convey a covered work, you may not impose any further +restrictions on the exercise of the rights granted or affirmed under +this License. For example, you may not impose a license fee, royalty, +or other charge for exercise of rights granted under this License, and +you may not initiate litigation (including a cross-claim or +counterclaim in a lawsuit) alleging that any patent claim is infringed +by making, using, selling, offering for sale, or importing the Program +or any portion of it. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combined work as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Lesser General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail +to address new problems or concerns. + + Each version is given a distinguishing version number. If the Library as +you received it specifies that a certain numbered version of the GNU Lesser +General Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that published +version or of any later version published by the Free Software Foundation. +If the Library as you received it does not specify a version number of the +GNU Lesser General Public License, you may choose any version of the GNU +Lesser General Public License ever published by the Free Software Foundation. - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - C++ Unix Bindings. C++ Unix Bindings for the serial library. - Copyright (C) 2024 Paul, Max - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the Library. + + END OF TERMS AND CONDITIONS + + diff --git a/integration_tests/.gitignore b/integration_tests/.gitignore new file mode 100644 index 0000000..2b0be97 --- /dev/null +++ b/integration_tests/.gitignore @@ -0,0 +1,6 @@ +# Deno +.deno/ +*.log + +# Test artifacts +*.test.ts.snap diff --git a/integration_tests/deno.json b/integration_tests/deno.json new file mode 100644 index 0000000..1a3548d --- /dev/null +++ b/integration_tests/deno.json @@ -0,0 +1,12 @@ +{ + "tasks": { + "test": "deno test --allow-ffi --allow-read --allow-env integration_test.ts" + }, + "imports": { + "@std/assert": "jsr:@std/assert@^1.0.16" + }, + "fmt": { + "lineWidth": 120, + "indentWidth": 4 + } +} diff --git a/integration_tests/deno.lock b/integration_tests/deno.lock new file mode 100644 index 0000000..50f2023 --- /dev/null +++ b/integration_tests/deno.lock @@ -0,0 +1,23 @@ +{ + "version": "5", + "specifiers": { + "jsr:@std/assert@^1.0.16": "1.0.16", + "jsr:@std/internal@^1.0.12": "1.0.12" + }, + "jsr": { + "@std/assert@1.0.16": { + "integrity": "6a7272ed1eaa77defe76e5ff63ca705d9c495077e2d5fd0126d2b53fc5bd6532", + "dependencies": [ + "jsr:@std/internal" + ] + }, + "@std/internal@1.0.12": { + "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" + } + }, + "workspace": { + "dependencies": [ + "jsr:@std/assert@^1.0.16" + ] + } +} diff --git a/integration_tests/ffi_bindings.ts b/integration_tests/ffi_bindings.ts new file mode 100644 index 0000000..6060b81 --- /dev/null +++ b/integration_tests/ffi_bindings.ts @@ -0,0 +1,76 @@ +/** + * Minimal FFI bindings for the cpp-bindings-linux shared library + * used by the Deno integration tests. + */ + +export type LoadedLibrary = Deno.DynamicLibrary; +export type SerialLib = LoadedLibrary["symbols"]; + +const symbols = { + serialOpen: { + parameters: ["pointer", "i32", "i32", "i32", "i32", "pointer"] as const, + result: "i64" as const, + }, + serialClose: { + parameters: ["i64", "pointer"] as const, + result: "i32" as const, + }, + serialRead: { + parameters: ["i64", "pointer", "i32", "i32", "i32", "pointer"] as const, + result: "i32" as const, + }, + serialWrite: { + parameters: ["i64", "pointer", "i32", "i32", "i32", "pointer"] as const, + result: "i32" as const, + }, +}; + +/** + * Load the cpp-bindings-linux shared library + * @param libraryPath Path to the .so file (defaults to build directory) + * @returns Object containing the symbols and a close method + */ +export async function loadSerialLib( + libraryPath?: string, +): Promise { + // Ensure this stays an async function for API stability + await Promise.resolve(); + + // Try to find the library in common build locations + const possiblePaths = [ + libraryPath, + "../build/libcpp_bindings_linux.so", + "../build/libcpp_bindings_linux.so.0", + "../build/libcpp_bindings_linux.so.0.0.0", + "../build/cpp_bindings_linux/libcpp_bindings_linux.so", + "./libcpp_bindings_linux.so", + "/usr/local/lib/libcpp_bindings_linux.so", + ].filter((p): p is string => p !== undefined); + + let lib: LoadedLibrary | null = null; + let lastError: Error | null = null; + + for (const path of possiblePaths) { + try { + const loaded = Deno.dlopen(path, symbols) as LoadedLibrary; + lib = loaded; + break; + } catch (error) { + lastError = error as Error; + continue; + } + } + + if (!lib) { + throw new Error( + `Failed to load cpp-bindings-linux library. Tried paths: ${ + possiblePaths.join(", ") + }. Last error: ${lastError?.message}`, + ); + } + + return lib; +} + +// Note: helpers for error callbacks and CString handling were removed +// here on purpose, as they are no longer needed by the minimal tests. diff --git a/integration_tests/integration_test.ts b/integration_tests/integration_test.ts new file mode 100644 index 0000000..806235f --- /dev/null +++ b/integration_tests/integration_test.ts @@ -0,0 +1,47 @@ +/** + * Minimal Deno integration tests for cpp-bindings-linux: + * - verify that the shared library can be loaded + * - verify that it can be cleanly unloaded again + */ + +import { assertExists } from "@std/assert"; +import { type LoadedLibrary, loadSerialLib, type SerialLib } from "./ffi_bindings.ts"; + +let lib: SerialLib | null = null; +let loadedLib: LoadedLibrary | null = null; + +Deno.test({ + name: "Load cpp-bindings-linux library", + async fn() { + // Small async boundary to keep Deno's async test happy + loadedLib = await loadSerialLib(); + assertExists(loadedLib, "Failed to load cpp-bindings-linux library"); + lib = loadedLib.symbols; + + // Sanity check: verify that the expected symbols exist + assertExists(lib.serialOpen); + assertExists(lib.serialClose); + assertExists(lib.serialRead); + assertExists(lib.serialWrite); + + console.log("cpp-bindings-linux library loaded and symbols resolved"); + }, + sanitizeResources: false, + sanitizeOps: false, +}); + +Deno.test({ + name: "Unload cpp-bindings-linux library", + async fn() { + // Simulate async cleanup boundary + await Promise.resolve(); + if (!loadedLib) return; + + loadedLib.close(); + loadedLib = null; + lib = null; + console.log("cpp-bindings-linux library unloaded successfully"); + }, + sanitizeResources: false, + sanitizeOps: false, +}); diff --git a/jsr/.gitignore b/jsr/.gitignore new file mode 100644 index 0000000..d062c58 --- /dev/null +++ b/jsr/.gitignore @@ -0,0 +1 @@ +jsr.json diff --git a/jsr/README.md b/jsr/README.md new file mode 100644 index 0000000..34ec660 --- /dev/null +++ b/jsr/README.md @@ -0,0 +1,24 @@ +# C++ Bindings Linux + +This package ships the native Linux shared library payload as a **JSON/base64 +blob**. + +## Usage + +Import the JSON and write the `.so` to disk (consumer project example): + +```ts +import blob from "@serial/cpp-bindings-linux/x84_64" with { type: "json" }; + +const bytes = new TextEncoder().encode(atob(blob.data)); + +const tempFilePath = Deno.makeTempFileSync(); +Deno.writeFileSync(tempFilePath, bytes, { mode: 0o755 }); + +// Now you can open the binary using for example `Deno.dlopen` +``` + +## License + +This package is licensed under **LGPL-3.0-only** (see the repository root +`LICENSE`). diff --git a/jsr/bin/x84_64.json b/jsr/bin/x84_64.json new file mode 100644 index 0000000..4184085 --- /dev/null +++ b/jsr/bin/x84_64.json @@ -0,0 +1,7 @@ +{ + "target": "linux-x86_64", + "filename": "libcpp_bindings_linux.so", + "encoding": "base64", + "sha256": "", + "data": "" +} diff --git a/jsr/jsr.json.in b/jsr/jsr.json.in new file mode 100644 index 0000000..3e8c0b5 --- /dev/null +++ b/jsr/jsr.json.in @@ -0,0 +1,18 @@ +{ + "name": "@serial/cpp-bindings-linux", + "version": "@PROJECT_VERSION@", + "license": "LGPL-3.0-only", + "description": "Linux shared-library bindings for Serial-IO/cpp-core (distributed via JSON/base64 because JSR has limited binary support).", + "exports": { + "./x84_64": "./bin/x84_64.json" + }, + "publish": { + "include": [ + "README.md", + "jsr.json", + "bin/**" + ] + } +} + + diff --git a/jsr/scripts/embed_so.ts b/jsr/scripts/embed_so.ts new file mode 100644 index 0000000..81cdafe --- /dev/null +++ b/jsr/scripts/embed_so.ts @@ -0,0 +1,51 @@ +// Usage: +// deno run --allow-read --allow-write jsr/scripts/embed_so.ts \ +// ./build/libcpp_bindings_linux.so ./jsr/bin/x84_64.json linux-x86_64 +// +// This converts the shared library into a JSON file containing base64 data for publishing to JSR. + +function bytesToHex(bytes: Uint8Array): string { + return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join(""); +} + +function base64FromBytes(bytes: Uint8Array): string { + // Chunk to avoid call stack limits in String.fromCharCode(...bigArray) + const chunkSize = 0x8000; + let binary = ""; + for (let i = 0; i < bytes.length; i += chunkSize) { + const chunk = bytes.subarray(i, i + chunkSize); + binary += String.fromCharCode(...chunk); + } + return btoa(binary); +} + +if (import.meta.main) { + const [inPath, outPath, target = "linux-x86_64"] = Deno.args; + if (!inPath || !outPath) { + console.error( + "Expected: [target]\nExample: build/libcpp_bindings_linux.so jsr/bin/x84_64.json linux-x86_64", + ); + Deno.exit(2); + } + + const bytes = await Deno.readFile(inPath); + const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)); + const sha256 = bytesToHex(digest); + + const filename = outPath.endsWith(".json") + ? (target === "linux-x86_64" + ? "libcpp_bindings_linux.so" + : "libcpp_bindings_linux.so") + : "libcpp_bindings_linux.so"; + + const payload = { + target, + filename, + encoding: "base64" as const, + sha256, + data: base64FromBytes(bytes), + }; + + await Deno.writeTextFile(outPath, JSON.stringify(payload)); + console.log(`Wrote ${outPath} (${bytes.length} bytes, sha256=${sha256})`); +} diff --git a/src/detail/posix_helpers.hpp b/src/detail/posix_helpers.hpp new file mode 100644 index 0000000..8a78f6a --- /dev/null +++ b/src/detail/posix_helpers.hpp @@ -0,0 +1,126 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace cpp_bindings_linux::detail +{ +class UniqueFd +{ + public: + UniqueFd() = default; + explicit UniqueFd(int in_fd) : fd_(in_fd) + { + } + + UniqueFd(const UniqueFd &) = delete; + auto operator=(const UniqueFd &) -> UniqueFd & = delete; + + UniqueFd(UniqueFd &&other) noexcept : fd_(other.fd_) + { + other.fd_ = -1; + } + auto operator=(UniqueFd &&other) noexcept -> UniqueFd & + { + if (this != &other) + { + reset(other.release()); + } + return *this; + } + + ~UniqueFd() + { + reset(-1); + } + + [[nodiscard]] auto get() const -> int + { + return fd_; + } + [[nodiscard]] auto valid() const -> bool + { + return fd_ >= 0; + } + + auto reset(int new_fd) -> void + { + if (fd_ >= 0) + { + close(fd_); + } + fd_ = new_fd; + } + + [[nodiscard]] auto release() -> int + { + const int out = fd_; + fd_ = -1; + return out; + } + + private: + int fd_ = -1; +}; + +template +inline auto invokeErrorCallback(Callback error_callback, cpp_core::StatusCodes code, const char *message) -> void +{ + if (error_callback != nullptr) + { + error_callback(static_cast(code), message); + } +} + +template +inline auto failMsg(Callback error_callback, cpp_core::StatusCodes code, const char *message) -> Ret +{ + invokeErrorCallback(error_callback, code, message); + return static_cast(code); +} + +template +inline auto failErrno(Callback error_callback, cpp_core::StatusCodes code) -> Ret +{ + if (error_callback != nullptr) + { + const std::string error_msg = std::error_code(errno, std::generic_category()).message(); + error_callback(static_cast(code), error_msg.c_str()); + } + return static_cast(code); +} + +// Poll helper used by read/write to implement timeouts. +// Returns: -1 on poll error, 0 on timeout/not-ready, 1 on ready. +inline auto waitFdReady(int file_descriptor, int timeout_ms, bool for_read) -> int +{ + struct pollfd poll_fd = {}; + poll_fd.fd = file_descriptor; + poll_fd.events = for_read ? POLLIN : POLLOUT; + poll_fd.revents = 0; + + const int poll_result = poll(&poll_fd, 1, timeout_ms); + if (poll_result < 0) + { + return -1; + } + if (poll_result == 0) + { + return 0; + } + if (for_read && ((poll_fd.revents & POLLIN) != 0)) + { + return 1; + } + if (!for_read && ((poll_fd.revents & POLLOUT) != 0)) + { + return 1; + } + return 0; +} +} // namespace cpp_bindings_linux::detail diff --git a/src/placeholder.cpp b/src/placeholder.cpp deleted file mode 100644 index 450b790..0000000 --- a/src/placeholder.cpp +++ /dev/null @@ -1,2 +0,0 @@ - -namespace name {} diff --git a/src/serial_close.cpp b/src/serial_close.cpp new file mode 100644 index 0000000..6ef9cea --- /dev/null +++ b/src/serial_close.cpp @@ -0,0 +1,33 @@ +#include +#include + +#include "detail/posix_helpers.hpp" + +#include +#include + +extern "C" +{ + + MODULE_API auto serialClose(int64_t handle, ErrorCallbackT error_callback) -> int + { + if (handle <= 0) + { + return static_cast(cpp_core::StatusCodes::kSuccess); + } + if (handle > std::numeric_limits::max()) + { + return cpp_bindings_linux::detail::failMsg(error_callback, cpp_core::StatusCodes::kInvalidHandleError, + "Invalid handle"); + } + + const int fd = static_cast(handle); + if (close(fd) != 0) + { + return cpp_bindings_linux::detail::failErrno(error_callback, cpp_core::StatusCodes::kCloseHandleError); + } + + return static_cast(cpp_core::StatusCodes::kSuccess); + } + +} // extern "C" diff --git a/src/serial_open.cpp b/src/serial_open.cpp new file mode 100644 index 0000000..e2ddd13 --- /dev/null +++ b/src/serial_open.cpp @@ -0,0 +1,164 @@ +#include +#include + +#include "detail/posix_helpers.hpp" + +#include +#include +#include +#include + +#ifndef TCGETS2 +#define TCGETS2 0x802C542A +#define TCSETS2 0x402C542B +#endif + +// Some libcs (or kernel headers) may not define BOTHER even if TCGETS2 exists. +// Define it here if missing so the build works in minimal environments +// (e.g., Deno's Debian-based CI containers). +#ifndef BOTHER +#define BOTHER 0x010000 +#endif + +// NOLINTBEGIN +// C Structure is defined by the kernel, so we cannot change it. +struct termios2 +{ + tcflag_t c_iflag; + tcflag_t c_oflag; + tcflag_t c_cflag; + tcflag_t c_lflag; + cc_t c_line; + cc_t c_cc[19]; + speed_t c_ispeed; + speed_t c_ospeed; +}; +// NOLINTEND + +extern "C" +{ + MODULE_API auto serialOpen(void *port, int baudrate, int data_bits, int parity, int stop_bits, + ErrorCallbackT error_callback) -> intptr_t + { + if (port == nullptr) + { + return cpp_bindings_linux::detail::failMsg(error_callback, cpp_core::StatusCodes::kNotFoundError, + "Port parameter is nullptr"); + } + + if (baudrate < 300) + { + return cpp_bindings_linux::detail::failMsg(error_callback, cpp_core::StatusCodes::kSetStateError, + "Invalid baudrate: must be >= 300"); + } + + if (data_bits < 5 || data_bits > 8) + { + return cpp_bindings_linux::detail::failMsg(error_callback, cpp_core::StatusCodes::kSetStateError, + "Invalid data bits: must be 5-8"); + } + + const char *port_path = static_cast(port); + + cpp_bindings_linux::detail::UniqueFd handle(open(port_path, O_RDWR | O_NOCTTY | O_NONBLOCK)); + if (!handle.valid()) + { + return cpp_bindings_linux::detail::failErrno(error_callback, + cpp_core::StatusCodes::kNotFoundError); + } + + struct termios2 tty = {}; + if (ioctl(handle.get(), TCGETS2, &tty) != 0) + { + return cpp_bindings_linux::detail::failErrno(error_callback, + cpp_core::StatusCodes::kGetStateError); + } + + tty.c_cflag &= ~CBAUD; + tty.c_cflag |= BOTHER; + tty.c_ispeed = baudrate; + tty.c_ospeed = baudrate; + + tty.c_cflag &= ~CSIZE; + switch (data_bits) + { + case 5: + tty.c_cflag |= CS5; + break; + case 6: + tty.c_cflag |= CS6; + break; + case 7: + tty.c_cflag |= CS7; + break; + case 8: + tty.c_cflag |= CS8; + break; + default: + return cpp_bindings_linux::detail::failMsg(error_callback, cpp_core::StatusCodes::kSetStateError, + "Invalid data bits"); + } + + tty.c_cflag &= ~(PARENB | PARODD); + switch (parity) + { + // parity mapping: + // 0 = no parity + // 1 = even parity + // 2 = odd parity + case 0: + break; + case 1: + tty.c_cflag |= PARENB; + break; + case 2: + tty.c_cflag |= (PARENB | PARODD); + break; + default: + return cpp_bindings_linux::detail::failMsg(error_callback, cpp_core::StatusCodes::kSetStateError, + "Invalid parity"); + } + + // stop_bits mapping: + // 0 or 1 = 1 stop bit (0 kept for backward compatibility with callers using "default") + // 2 = 2 stop bits + if (stop_bits != 0 && stop_bits != 1 && stop_bits != 2) + { + return cpp_bindings_linux::detail::failMsg(error_callback, cpp_core::StatusCodes::kSetStateError, + "Invalid stop bits: must be 0, 1, or 2"); + } + if (stop_bits == 2) + { + tty.c_cflag |= CSTOPB; + } + else + { + tty.c_cflag &= ~CSTOPB; + } + + tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + tty.c_iflag &= ~(IXON | IXOFF | IXANY | INLCR | IGNCR | ICRNL); + tty.c_oflag &= ~OPOST; + + tty.c_cc[VMIN] = 0; + tty.c_cc[VTIME] = 0; + + if (ioctl(handle.get(), TCSETS2, &tty) != 0) + { + return cpp_bindings_linux::detail::failErrno(error_callback, + cpp_core::StatusCodes::kSetStateError); + } + + // Keep O_NONBLOCK enabled. Our read/write APIs implement timeouts via poll(), + // and leaving the FD non-blocking prevents any unexpected blocking syscalls. + + tcflush(handle.get(), TCIOFLUSH); + + // Note: Some devices (e.g., Arduino) reset when the serial port is opened. + // It is recommended to wait 1-2 seconds after opening before sending data + // to allow the device to initialize. + + return static_cast(handle.release()); + } + +} // extern "C" diff --git a/src/serial_read.cpp b/src/serial_read.cpp new file mode 100644 index 0000000..a883b80 --- /dev/null +++ b/src/serial_read.cpp @@ -0,0 +1,97 @@ +#include +#include + +#include "detail/posix_helpers.hpp" + +#include +#include +#include + +extern "C" +{ + MODULE_API auto serialRead(int64_t handle, void *buffer, int buffer_size, int timeout_ms, int /*multiplier*/, + ErrorCallbackT error_callback) -> int + { + if (buffer == nullptr || buffer_size <= 0) + { + return cpp_bindings_linux::detail::failMsg(error_callback, cpp_core::StatusCodes::kBufferError, + "Invalid buffer or buffer_size"); + } + + if (handle <= 0 || handle > std::numeric_limits::max()) + { + return cpp_bindings_linux::detail::failMsg(error_callback, cpp_core::StatusCodes::kInvalidHandleError, + "Invalid handle"); + } + + const int fd = static_cast(handle); + auto *buf = static_cast(buffer); + + const int ready = cpp_bindings_linux::detail::waitFdReady(fd, timeout_ms, true); + if (ready < 0) + { + return cpp_bindings_linux::detail::failErrno(error_callback, cpp_core::StatusCodes::kReadError); + } + if (ready == 0) + { + return 0; + } + + const auto try_read_once = [&](unsigned char *dst, int size) -> ssize_t { + const ssize_t bytes = ::read(fd, dst, size); + if (bytes < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) + { + return 0; + } + return bytes; + }; + + ssize_t bytes_read = try_read_once(buf, buffer_size); + if (bytes_read < 0) + { + return cpp_bindings_linux::detail::failErrno(error_callback, cpp_core::StatusCodes::kReadError); + } + + // Some drivers can report readiness but still return 0; give it a tiny grace period and retry once. + if (bytes_read == 0) + { + const int retry_ready = cpp_bindings_linux::detail::waitFdReady(fd, 10, true); + if (retry_ready < 0) + { + return cpp_bindings_linux::detail::failErrno(error_callback, cpp_core::StatusCodes::kReadError); + } + if (retry_ready == 0) + { + return 0; + } + bytes_read = try_read_once(buf, buffer_size); + if (bytes_read <= 0) + { + return 0; + } + } + + int total_read = static_cast(bytes_read); + while (total_read < buffer_size) + { + const int loop_ready = cpp_bindings_linux::detail::waitFdReady(fd, 0, true); + if (loop_ready < 0) + { + return cpp_bindings_linux::detail::failErrno(error_callback, cpp_core::StatusCodes::kReadError); + } + if (loop_ready == 0) + { + break; + } + const ssize_t more_bytes = try_read_once(buf + total_read, buffer_size - total_read); + if (more_bytes <= 0) + { + break; + } + total_read += static_cast(more_bytes); + } + + return total_read; + } + +} // extern "C" diff --git a/src/serial_write.cpp b/src/serial_write.cpp new file mode 100644 index 0000000..abd7b10 --- /dev/null +++ b/src/serial_write.cpp @@ -0,0 +1,64 @@ +#include +#include + +#include "detail/posix_helpers.hpp" + +#include +#include +#include +#include +#include + +extern "C" +{ + + MODULE_API auto serialWrite(int64_t handle, const void *buffer, int buffer_size, int timeout_ms, int /*multiplier*/, + ErrorCallbackT error_callback) -> int + { + if (buffer == nullptr || buffer_size <= 0) + { + return cpp_bindings_linux::detail::failMsg(error_callback, cpp_core::StatusCodes::kBufferError, + "Invalid buffer or buffer_size"); + } + + if (handle <= 0 || handle > std::numeric_limits::max()) + { + return cpp_bindings_linux::detail::failMsg(error_callback, cpp_core::StatusCodes::kInvalidHandleError, + "Invalid handle"); + } + + const int fd = static_cast(handle); + + ssize_t bytes_written = ::write(fd, buffer, buffer_size); + if (bytes_written < 0) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + { + const int ready = cpp_bindings_linux::detail::waitFdReady(fd, timeout_ms, false); + if (ready < 0) + { + return cpp_bindings_linux::detail::failErrno(error_callback, + cpp_core::StatusCodes::kWriteError); + } + if (ready > 0) + { + bytes_written = ::write(fd, buffer, buffer_size); + } + else + { + return 0; + } + } + + if (bytes_written < 0) + { + return cpp_bindings_linux::detail::failErrno(error_callback, cpp_core::StatusCodes::kWriteError); + } + } + + tcdrain(fd); + + return static_cast(bytes_written); + } + +} // extern "C" diff --git a/tests/test_serial_arduino.cpp b/tests/test_serial_arduino.cpp new file mode 100644 index 0000000..6772c4a --- /dev/null +++ b/tests/test_serial_arduino.cpp @@ -0,0 +1,115 @@ +// Test for serial communication with Arduino echo script on /dev/ttyUSB0 + +#include +#include +#include +#include +#include +#include +#include + +#include + +class SerialArduinoTest : public ::testing::Test +{ + protected: + void SetUp() override + { + const char *env_port = std::getenv("SERIAL_TEST_PORT"); + const char *port = env_port != nullptr ? env_port : "/dev/ttyUSB0"; + handle_ = serialOpen(const_cast(static_cast(port)), 115200, 8, 0, 0, nullptr); + + if (handle_ <= 0) + { + GTEST_SKIP() << "Could not open serial port '" << port + << "'. Set SERIAL_TEST_PORT or connect Arduino on /dev/ttyUSB0."; + } + + usleep(2000000); + } + + void TearDown() override + { + if (handle_ > 0) + { + serialClose(handle_, nullptr); + handle_ = 0; + } + } + + intptr_t handle_ = 0; +}; + +TEST_F(SerialArduinoTest, OpenClose) +{ + EXPECT_GT(handle_, 0) << "serialOpen should return a positive handle"; +} + +TEST_F(SerialArduinoTest, WriteReadEcho) +{ + const char *test_message = "Hello Arduino!\n"; + int message_len = static_cast(strlen(test_message)); + + int written = serialWrite(handle_, test_message, message_len, 1000, 1, nullptr); + EXPECT_EQ(written, message_len) << "Should write all bytes. Written: " << written << ", Expected: " << message_len; + + usleep(500000); + + char read_buffer[256] = {0}; + int read_bytes = serialRead(handle_, read_buffer, sizeof(read_buffer) - 1, 2000, 1, nullptr); + + EXPECT_GT(read_bytes, 0) << "Should read at least some bytes"; + EXPECT_LE(read_bytes, static_cast(sizeof(read_buffer) - 1)) << "Should not overflow buffer"; + + read_buffer[read_bytes] = '\0'; + EXPECT_STRNE(read_buffer, "") << "Should receive echo from Arduino"; +} + +TEST_F(SerialArduinoTest, MultipleEchoCycles) +{ + const char *messages[] = {"Test1\n", "Test2\n", "Test3\n"}; + const int num_messages = 3; + + for (int i = 0; i < num_messages; ++i) + { + int msg_len = static_cast(strlen(messages[i])); + + int written = serialWrite(handle_, messages[i], msg_len, 1000, 1, nullptr); + EXPECT_EQ(written, msg_len) << "Cycle " << i << ": write failed"; + + usleep(500000); + + char read_buffer[256] = {0}; + int read_bytes = serialRead(handle_, read_buffer, sizeof(read_buffer) - 1, 2000, 1, nullptr); + EXPECT_GT(read_bytes, 0) << "Cycle " << i << ": read failed"; + } +} + +TEST_F(SerialArduinoTest, ReadTimeout) +{ + char buffer[256]; + int read_bytes = serialRead(handle_, buffer, sizeof(buffer), 100, 1, nullptr); + EXPECT_GE(read_bytes, 0) << "Timeout should return 0, not error"; +} + +TEST(SerialInvalidHandleTest, InvalidHandleRead) +{ + char buffer[256]; + int result = serialRead(-1, buffer, sizeof(buffer), 1000, 1, nullptr); + EXPECT_EQ(result, static_cast(cpp_core::StatusCodes::kInvalidHandleError)) + << "Should return error for invalid handle"; +} + +TEST(SerialInvalidHandleTest, InvalidHandleWrite) +{ + const char *data = "test"; + int result = serialWrite(-1, data, 4, 1000, 1, nullptr); + EXPECT_EQ(result, static_cast(cpp_core::StatusCodes::kInvalidHandleError)) + << "Should return error for invalid handle"; +} + +TEST(SerialInvalidHandleTest, InvalidHandleClose) +{ + int result = serialClose(-1, nullptr); + EXPECT_EQ(result, static_cast(cpp_core::StatusCodes::kSuccess)); +}