/* * Keyman is copyright (C) SIL International. MIT License. * * Tiny frame console app to compile a .kmx from a .kmn */ #include #include #include #include #include #include #include #include #include #include "util_filesystem.h" #include "util_callbacks.h" #ifdef _MSC_VER #else #include #include #endif using namespace std; int main(int argc, char *argv[]) { if(argc < 4) { puts("Usage: kmcompxtest source.kmn output.kmx fixture.kmx"); return __LINE__; } char* kmn_file = argv[1]; char* kmx_file = argv[2]; char* reference_kmx = argv[3]; // Replace \ with / for(char *p = kmn_file; *p; p++) { if(*p == '\\') *p = '/'; } for(char *p = kmx_file; *p; p++) { if(*p == '\\') *p = '/'; } for(char *p = reference_kmx; *p; p++) { if(*p == '\\') *p = '/'; } const char* first5 = "CERR_"; KMCMP_COMPILER_RESULT result; KMCMP_COMPILER_OPTIONS options; options.saveDebug = true; options.compilerWarningsAsErrors = false; options.warnDeprecatedCode = true; options.shouldAddCompilerVersion = false; options.target = CKF_KEYMAN; if(kmcmp_CompileKeyboard(kmn_file, options, msgproc, loadfileProc, nullptr, result)) { char* testname = strrchr( (char*) kmn_file, '/') + 1; if(strncmp(testname, first5, strlen(first5)) == 0){ return __LINE__; // exit code: CERR_ in Name + no Error found } // On non-win32 platforms, we cannot get kmcmpdll.dll to build keyboards // legacy-mode, so we'll compare to a hopefully existing file that we've // been passed FILE* fp1 = Open_File(kmx_file, "wb"); if(!fp1) return __LINE__; // Write out for reference fwrite(result.kmx, 1, result.kmxSize, fp1); fclose(fp1); FILE* fp2 = Open_File(reference_kmx, "rb"); if(!fp2) return __LINE__; // exit code: fail if no reference kmx file in build-folder fseek(fp2, 0, SEEK_END); auto sz2 = ftell(fp2); fseek(fp2, 0, SEEK_SET); if (result.kmxSize != (size_t)sz2) return __LINE__; // exit code: size of kmx-file in build differs from size of kmx-file in source folder char* buf2 = new char[result.kmxSize]; auto sz3 = fread(buf2, 1, result.kmxSize, fp2); if (result.kmxSize != sz3) return __LINE__; // exit code: when not able to read the build into the buffer return memcmp(result.kmx, buf2, result.kmxSize) ? __LINE__ : 0; // exit code: when contents of kmx-file in build differs from contents of kmx-file in source folder // success: when contents of kmx-file in build and source folder are the same } else { /*if Errors found: check number (e.g. CERR_4061_balochi_phonetic.kmn should produce Error 4061)*/ int error_val = 0; char* testname = strrchr( (char*) kmn_file, '/') + 1; // Does testname contain CERR_ && contains '_' on pos 9 ? -> Get ErrorValue if ((strncmp(testname, first5, strlen(first5)) == 0) && (testname[9] == '_')) { char* ErrNr = strchr(testname, '_') +1 ; std::istringstream(ErrNr) >> std::hex >> error_val; // check if error_val is in Array of Errors; if it is found return 0 (it's not an error) for (size_t i = 0; i < error_vec.size() ; i++) { if (error_vec[i] == error_val) { return 0; // success: CERR_ in Name + Error (specified in CERR_Name) IS found } } return __LINE__; // exit code: CERR_ in Name + Error (specified in CERR_Name) is NOT found } return __LINE__; // exit code: no correct CERR_ in Name + CompileKeyboardFile failed (i.e. test config is invalid) } } bool equalIgnoreCase(const string& a, const string& b) { return std::equal( a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) { return tolower(a) == tolower(b); } ); } // Returns TRUE if the keyboard has a &TARGETS store with // 'any', 'windows', 'macosx', 'linux', or 'desktop' targets, // or if no &TARGETS store exists (which is equivalent to // 'windows'). bool isDesktopKeyboard(FILE* fp) { COMP_KEYBOARD fk; if(fseek(fp, 0, SEEK_SET) != 0) { printf("isDesktopKeyboard: fseek(0) failed\n"); exit(1); } if(!fread(&fk, sizeof(fk), 1, fp)) { printf("isDesktopKeyboard failed: could not read COMP_KEYBOARD\n"); exit(1); } // Note: this code assumes a valid .kmx. This is okay for a unit test if(fseek(fp, fk.dpStoreArray, SEEK_SET) != 0) { printf("isDesktopKeyboard failed: could not locate fk.dpStoreArray=%x\n", fk.dpStoreArray); exit(1); } PCOMP_STORE pfs = new COMP_STORE[fk.cxStoreArray]; if(fread(pfs, sizeof(COMP_STORE), fk.cxStoreArray, fp) != fk.cxStoreArray) { printf("isDesktopKeyboard failed: could not read fk.dpStoreArray=%x [%x]\n", fk.dpStoreArray, fk.cxStoreArray); exit(1); } PCOMP_STORE s = pfs; for(KMX_DWORD i = 0; i < fk.cxStoreArray; i++, s++) { if(s->dwSystemID != TSS_TARGETS) { continue; } // Read targets store if(fseek(fp, s->dpString, SEEK_SET) != 0) { printf("isDesktopKeyboard: could not seek to TSS_TARGETS.dpString %x\n", s->dpString); exit(1); } KMX_WCHAR buf[256]; if(fread(buf, sizeof(KMX_WCHAR), 256, fp) == 0) { printf("isDesktopKeyboard: could not read TSS_TARGETS.dpString %x\n", s->dpString); exit(1); } buf[255] = 0; // sanity if(fseek(fp, 0, SEEK_SET) != 0) { printf("isDesktopKeyboard: fseek(0) failed\n"); exit(1); } std::string targets1 = string_from_u16string(buf); std::stringstream targets(targets1); std::string target; printf("isDesktopKeyboard: TSS_TARGETS: %s\n", targets1.c_str()); delete[] pfs; while(targets >> target) { //Matches KMXKeymanTargets from UKeymanTargets.pas if(equalIgnoreCase(target, "any") || equalIgnoreCase(target, "windows") || equalIgnoreCase(target, "macosx") || equalIgnoreCase(target, "linux") || equalIgnoreCase(target, "desktop") ) { printf("isDesktopKeyboard: TSS_TARGETS does contain desktop target %s\n", target.c_str()); return true; } } printf("isDesktopKeyboard: TSS_TARGETS does not contain desktop target\n"); return false; } delete[] pfs; if(fseek(fp, 0, SEEK_SET) != 0) { printf("isDesktopKeyboard: fseek(0) failed\n"); exit(1); } printf("isDesktopKeyboard: TSS_TARGETS does not exist\n"); return true; }