cmGlobalGhsMultiGenerator.cxx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  1. /*============================================================================
  2. CMake - Cross Platform Makefile Generator
  3. Copyright 2015 Geoffrey Viola <[email protected]>
  4. Distributed under the OSI-approved BSD License (the "License");
  5. see accompanying file Copyright.txt for details.
  6. This software is distributed WITHOUT ANY WARRANTY; without even the
  7. implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  8. See the License for more information.
  9. ============================================================================*/
  10. #include "cmGlobalGhsMultiGenerator.h"
  11. #include "cmLocalGhsMultiGenerator.h"
  12. #include "cmMakefile.h"
  13. #include "cmVersion.h"
  14. #include "cmGeneratedFileStream.h"
  15. #include "cmGhsMultiTargetGenerator.h"
  16. #include <cmsys/SystemTools.hxx>
  17. #include <cmAlgorithms.h>
  18. const char *cmGlobalGhsMultiGenerator::FILE_EXTENSION = ".gpj";
  19. const char *cmGlobalGhsMultiGenerator::DEFAULT_MAKE_PROGRAM = "gbuild";
  20. cmGlobalGhsMultiGenerator::cmGlobalGhsMultiGenerator(cmake* cm)
  21. : cmGlobalGenerator(cm), OSDirRelative(false)
  22. {
  23. this->GhsBuildCommandInitialized = false;
  24. }
  25. cmGlobalGhsMultiGenerator::~cmGlobalGhsMultiGenerator()
  26. {
  27. cmDeleteAll(TargetFolderBuildStreams);
  28. }
  29. cmLocalGenerator *
  30. cmGlobalGhsMultiGenerator::CreateLocalGenerator(cmMakefile* mf)
  31. {
  32. return new cmLocalGhsMultiGenerator(this, mf);
  33. }
  34. void cmGlobalGhsMultiGenerator::GetDocumentation(cmDocumentationEntry &entry)
  35. {
  36. entry.Name = GetActualName();
  37. entry.Brief =
  38. "Generates Green Hills MULTI files (experimental, work-in-progress).";
  39. }
  40. void cmGlobalGhsMultiGenerator::EnableLanguage(
  41. std::vector<std::string> const &l, cmMakefile *mf, bool optional)
  42. {
  43. mf->AddDefinition("CMAKE_SYSTEM_NAME", "GHS-MULTI");
  44. mf->AddDefinition("CMAKE_SYSTEM_PROCESSOR", "ARM");
  45. const std::string ghsCompRoot(GetCompRoot());
  46. mf->AddDefinition("GHS_COMP_ROOT", ghsCompRoot.c_str());
  47. std::string ghsCompRootStart =
  48. 0 == ghsCompRootStart.size() ? "" : ghsCompRoot + "/";
  49. mf->AddDefinition("CMAKE_C_COMPILER",
  50. std::string(ghsCompRootStart + "ccarm.exe").c_str());
  51. mf->AddDefinition("CMAKE_C_COMPILER_ID_RUN", "TRUE");
  52. mf->AddDefinition("CMAKE_C_COMPILER_ID", "GHS");
  53. mf->AddDefinition("CMAKE_C_COMPILER_FORCED", "TRUE");
  54. mf->AddDefinition("CMAKE_CXX_COMPILER",
  55. std::string(ghsCompRootStart + "cxarm.exe").c_str());
  56. mf->AddDefinition("CMAKE_CXX_COMPILER_ID_RUN", "TRUE");
  57. mf->AddDefinition("CMAKE_CXX_COMPILER_ID", "GHS");
  58. mf->AddDefinition("CMAKE_CXX_COMPILER_FORCED", "TRUE");
  59. if (!ghsCompRoot.empty())
  60. {
  61. static const char *compPreFix = "comp_";
  62. std::string compFilename =
  63. cmsys::SystemTools::FindLastString(ghsCompRoot.c_str(), compPreFix);
  64. cmsys::SystemTools::ReplaceString(compFilename, compPreFix, "");
  65. mf->AddDefinition("CMAKE_SYSTEM_VERSION", compFilename.c_str());
  66. }
  67. mf->AddDefinition("GHSMULTI", "1"); // identifier for user CMake files
  68. this->cmGlobalGenerator::EnableLanguage(l, mf, optional);
  69. }
  70. void cmGlobalGhsMultiGenerator::FindMakeProgram(cmMakefile *mf)
  71. {
  72. // The GHS generator knows how to lookup its build tool
  73. // directly instead of needing a helper module to do it, so we
  74. // do not actually need to put CMAKE_MAKE_PROGRAM into the cache.
  75. if (cmSystemTools::IsOff(mf->GetDefinition("CMAKE_MAKE_PROGRAM")))
  76. {
  77. mf->AddDefinition("CMAKE_MAKE_PROGRAM",
  78. this->GetGhsBuildCommand().c_str());
  79. }
  80. }
  81. std::string const &cmGlobalGhsMultiGenerator::GetGhsBuildCommand()
  82. {
  83. if (!this->GhsBuildCommandInitialized)
  84. {
  85. this->GhsBuildCommandInitialized = true;
  86. this->GhsBuildCommand = this->FindGhsBuildCommand();
  87. }
  88. return this->GhsBuildCommand;
  89. }
  90. std::string cmGlobalGhsMultiGenerator::FindGhsBuildCommand()
  91. {
  92. std::vector<std::string> userPaths;
  93. userPaths.push_back(this->GetCompRoot());
  94. std::string makeProgram =
  95. cmSystemTools::FindProgram(DEFAULT_MAKE_PROGRAM, userPaths);
  96. if (makeProgram.empty())
  97. {
  98. makeProgram = DEFAULT_MAKE_PROGRAM;
  99. }
  100. return makeProgram;
  101. }
  102. std::string cmGlobalGhsMultiGenerator::GetCompRoot()
  103. {
  104. std::string output;
  105. const std::vector<std::string>
  106. potentialDirsHardPaths(GetCompRootHardPaths());
  107. const std::vector<std::string> potentialDirsRegistry(GetCompRootRegistry());
  108. std::vector<std::string> potentialDirsComplete;
  109. potentialDirsComplete.insert(potentialDirsComplete.end(),
  110. potentialDirsHardPaths.begin(),
  111. potentialDirsHardPaths.end());
  112. potentialDirsComplete.insert(potentialDirsComplete.end(),
  113. potentialDirsRegistry.begin(),
  114. potentialDirsRegistry.end());
  115. // Use latest version
  116. std::string outputDirName;
  117. for (std::vector<std::string>::const_iterator potentialDirsCompleteIt =
  118. potentialDirsComplete.begin();
  119. potentialDirsCompleteIt != potentialDirsComplete.end();
  120. ++potentialDirsCompleteIt)
  121. {
  122. const std::string dirName(
  123. cmsys::SystemTools::GetFilenameName(*potentialDirsCompleteIt));
  124. if (dirName.compare(outputDirName) > 0)
  125. {
  126. output = *potentialDirsCompleteIt;
  127. outputDirName = dirName;
  128. }
  129. }
  130. return output;
  131. }
  132. std::vector<std::string> cmGlobalGhsMultiGenerator::GetCompRootHardPaths()
  133. {
  134. std::vector<std::string> output;
  135. cmSystemTools::Glob("C:/ghs", "comp_[^;]+", output);
  136. for (std::vector<std::string>::iterator outputIt = output.begin();
  137. outputIt != output.end(); ++outputIt)
  138. {
  139. *outputIt = "C:/ghs/" + *outputIt;
  140. }
  141. return output;
  142. }
  143. std::vector<std::string> cmGlobalGhsMultiGenerator::GetCompRootRegistry()
  144. {
  145. std::vector<std::string> output(2);
  146. cmsys::SystemTools::ReadRegistryValue(
  147. "HKEY_LOCAL_"
  148. "MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\"
  149. "Windows\\CurrentVersion\\Uninstall\\"
  150. "GreenHillsSoftwared771f1b4;InstallLocation",
  151. output[0]);
  152. cmsys::SystemTools::ReadRegistryValue(
  153. "HKEY_LOCAL_"
  154. "MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\"
  155. "Windows\\CurrentVersion\\Uninstall\\"
  156. "GreenHillsSoftware9881cef6;InstallLocation",
  157. output[1]);
  158. return output;
  159. }
  160. void cmGlobalGhsMultiGenerator::OpenBuildFileStream(
  161. std::string const &filepath, cmGeneratedFileStream **filestream)
  162. {
  163. // Get a stream where to generate things.
  164. if (NULL == *filestream)
  165. {
  166. *filestream = new cmGeneratedFileStream(filepath.c_str());
  167. if (NULL != *filestream)
  168. {
  169. OpenBuildFileStream(*filestream);
  170. }
  171. }
  172. }
  173. void cmGlobalGhsMultiGenerator::OpenBuildFileStream(
  174. cmGeneratedFileStream *filestream)
  175. {
  176. *filestream << "#!gbuild" << std::endl;
  177. }
  178. void cmGlobalGhsMultiGenerator::OpenBuildFileStream()
  179. {
  180. // Compute GHS MULTI's build file path.
  181. std::string buildFilePath =
  182. this->GetCMakeInstance()->GetHomeOutputDirectory();
  183. buildFilePath += "/";
  184. buildFilePath += "default";
  185. buildFilePath += FILE_EXTENSION;
  186. this->Open(std::string(""), buildFilePath, &this->TargetFolderBuildStreams);
  187. OpenBuildFileStream(GetBuildFileStream());
  188. char const *osDir =
  189. this->GetCMakeInstance()->GetCacheDefinition("GHS_OS_DIR");
  190. if (NULL == osDir)
  191. {
  192. osDir = "";
  193. cmSystemTools::Error("GHS_OS_DIR cache variable must be set");
  194. }
  195. else
  196. {
  197. this->GetCMakeInstance()->MarkCliAsUsed("GHS_OS_DIR");
  198. }
  199. std::string fOSDir(this->trimQuotes(osDir));
  200. cmSystemTools::ReplaceString(fOSDir, "\\", "/");
  201. if (!fOSDir.empty() && ('c' == fOSDir[0] || 'C' == fOSDir[0]))
  202. {
  203. this->OSDirRelative = false;
  204. }
  205. else
  206. {
  207. this->OSDirRelative = true;
  208. }
  209. char const *bspName =
  210. this->GetCMakeInstance()->GetCacheDefinition("GHS_BSP_NAME");
  211. if (NULL == bspName)
  212. {
  213. bspName = "";
  214. cmSystemTools::Error("GHS_BSP_NAME cache variable must be set");
  215. }
  216. else
  217. {
  218. this->GetCMakeInstance()->MarkCliAsUsed("GHS_BSP_NAME");
  219. }
  220. std::string fBspName(this->trimQuotes(bspName));
  221. cmSystemTools::ReplaceString(fBspName, "\\", "/");
  222. this->WriteMacros();
  223. this->WriteHighLevelDirectives();
  224. GhsMultiGpj::WriteGpjTag(GhsMultiGpj::PROJECT, this->GetBuildFileStream());
  225. this->WriteDisclaimer(this->GetBuildFileStream());
  226. *this->GetBuildFileStream() << "# Top Level Project File" << std::endl;
  227. if (!fBspName.empty())
  228. {
  229. *this->GetBuildFileStream() << " -bsp " << fBspName << std::endl;
  230. }
  231. this->WriteCompilerOptions(fOSDir);
  232. }
  233. void cmGlobalGhsMultiGenerator::CloseBuildFileStream(
  234. cmGeneratedFileStream **filestream)
  235. {
  236. if (filestream)
  237. {
  238. delete *filestream;
  239. *filestream = NULL;
  240. }
  241. else
  242. {
  243. cmSystemTools::Error("Build file stream was not open.");
  244. }
  245. }
  246. void cmGlobalGhsMultiGenerator::Generate()
  247. {
  248. this->cmGlobalGenerator::Generate();
  249. if (!this->LocalGenerators.empty())
  250. {
  251. this->OpenBuildFileStream();
  252. // Build all the folder build files
  253. for (unsigned int i = 0; i < this->LocalGenerators.size(); ++i)
  254. {
  255. cmLocalGhsMultiGenerator *lg =
  256. static_cast<cmLocalGhsMultiGenerator *>(this->LocalGenerators[i]);
  257. std::vector<cmGeneratorTarget*> tgts = lg->GetGeneratorTargets();
  258. this->UpdateBuildFiles(tgts);
  259. }
  260. }
  261. cmDeleteAll(TargetFolderBuildStreams);
  262. this->TargetFolderBuildStreams.clear();
  263. }
  264. void cmGlobalGhsMultiGenerator::GenerateBuildCommand(
  265. std::vector<std::string> &makeCommand, const std::string &makeProgram,
  266. const std::string & /*projectName*/, const std::string & /*projectDir*/,
  267. const std::string &targetName, const std::string & /*config*/,
  268. bool /*fast*/, bool /*verbose*/,
  269. std::vector<std::string> const &makeOptions)
  270. {
  271. makeCommand.push_back(
  272. this->SelectMakeProgram(makeProgram, this->GetGhsBuildCommand())
  273. );
  274. makeCommand.insert(makeCommand.end(),
  275. makeOptions.begin(), makeOptions.end());
  276. if (!targetName.empty())
  277. {
  278. if (targetName == "clean")
  279. {
  280. makeCommand.push_back("-clean");
  281. }
  282. else
  283. {
  284. makeCommand.push_back(targetName);
  285. }
  286. }
  287. }
  288. void cmGlobalGhsMultiGenerator::WriteMacros()
  289. {
  290. char const *ghsGpjMacros =
  291. this->GetCMakeInstance()->GetCacheDefinition("GHS_GPJ_MACROS");
  292. if (NULL != ghsGpjMacros)
  293. {
  294. std::vector<std::string> expandedList;
  295. cmSystemTools::ExpandListArgument(std::string(ghsGpjMacros), expandedList);
  296. for (std::vector<std::string>::const_iterator expandedListI =
  297. expandedList.begin();
  298. expandedListI != expandedList.end(); ++expandedListI)
  299. {
  300. *this->GetBuildFileStream() << "macro " << *expandedListI << std::endl;
  301. }
  302. }
  303. }
  304. void cmGlobalGhsMultiGenerator::WriteHighLevelDirectives()
  305. {
  306. *this->GetBuildFileStream() << "primaryTarget=arm_integrity.tgt"
  307. << std::endl;
  308. char const *const customization =
  309. this->GetCMakeInstance()->GetCacheDefinition("GHS_CUSTOMIZATION");
  310. if (NULL != customization && strlen(customization) > 0)
  311. {
  312. *this->GetBuildFileStream() << "customization="
  313. << trimQuotes(customization)
  314. << std::endl;
  315. this->GetCMakeInstance()->MarkCliAsUsed("GHS_CUSTOMIZATION");
  316. }
  317. }
  318. void cmGlobalGhsMultiGenerator::WriteCompilerOptions(std::string const &fOSDir)
  319. {
  320. *this->GetBuildFileStream() << " -os_dir=\"" << fOSDir << "\""
  321. << std::endl;
  322. }
  323. void cmGlobalGhsMultiGenerator::WriteDisclaimer(std::ostream *os)
  324. {
  325. (*os) << "#" << std::endl
  326. << "# CMAKE generated file: DO NOT EDIT!" << std::endl
  327. << "# Generated by \"" << GetActualName() << "\""
  328. << " Generator, CMake Version " << cmVersion::GetMajorVersion() << "."
  329. << cmVersion::GetMinorVersion() << std::endl
  330. << "#" << std::endl;
  331. }
  332. void cmGlobalGhsMultiGenerator::AddFilesUpToPath(
  333. cmGeneratedFileStream *mainBuildFile,
  334. std::map<std::string, cmGeneratedFileStream *> *targetFolderBuildStreams,
  335. char const *homeOutputDirectory, std::string const &path,
  336. GhsMultiGpj::Types projType, std::string const &relPath)
  337. {
  338. std::string workingPath(path);
  339. cmSystemTools::ConvertToUnixSlashes(workingPath);
  340. std::vector<cmsys::String> splitPath =
  341. cmSystemTools::SplitString(workingPath);
  342. std::string workingRelPath(relPath);
  343. cmSystemTools::ConvertToUnixSlashes(workingRelPath);
  344. if (!workingRelPath.empty())
  345. {
  346. workingRelPath += "/";
  347. }
  348. std::string pathUpTo;
  349. for (std::vector<cmsys::String>::const_iterator splitPathI =
  350. splitPath.begin();
  351. splitPath.end() != splitPathI; ++splitPathI)
  352. {
  353. pathUpTo += *splitPathI;
  354. if (targetFolderBuildStreams->end() ==
  355. targetFolderBuildStreams->find(pathUpTo))
  356. {
  357. AddFilesUpToPathNewBuildFile(
  358. mainBuildFile, targetFolderBuildStreams, homeOutputDirectory,
  359. pathUpTo, splitPath.begin() == splitPathI, workingRelPath, projType);
  360. }
  361. AddFilesUpToPathAppendNextFile(targetFolderBuildStreams, pathUpTo,
  362. splitPathI, splitPath.end(), projType);
  363. pathUpTo += "/";
  364. }
  365. }
  366. void cmGlobalGhsMultiGenerator::Open(
  367. std::string const &mapKeyName, std::string const &fileName,
  368. std::map<std::string, cmGeneratedFileStream *> *fileMap)
  369. {
  370. if (fileMap->end() == fileMap->find(fileName))
  371. {
  372. cmGeneratedFileStream *temp(new cmGeneratedFileStream);
  373. temp->open(fileName.c_str());
  374. (*fileMap)[mapKeyName] = temp;
  375. }
  376. }
  377. void cmGlobalGhsMultiGenerator::AddFilesUpToPathNewBuildFile(
  378. cmGeneratedFileStream *mainBuildFile,
  379. std::map<std::string, cmGeneratedFileStream *> *targetFolderBuildStreams,
  380. char const *homeOutputDirectory, std::string const &pathUpTo,
  381. bool const isFirst, std::string const &relPath,
  382. GhsMultiGpj::Types const projType)
  383. {
  384. // create folders up to file path
  385. std::string absPath = std::string(homeOutputDirectory) + "/" + relPath;
  386. std::string newPath = absPath + pathUpTo;
  387. if (!cmSystemTools::FileExists(newPath.c_str()))
  388. {
  389. cmSystemTools::MakeDirectory(newPath.c_str());
  390. }
  391. // Write out to filename for first time
  392. std::string relFilename(GetFileNameFromPath(pathUpTo));
  393. std::string absFilename = absPath + relFilename;
  394. Open(pathUpTo, absFilename, targetFolderBuildStreams);
  395. OpenBuildFileStream((*targetFolderBuildStreams)[pathUpTo]);
  396. GhsMultiGpj::WriteGpjTag(projType, (*targetFolderBuildStreams)[pathUpTo]);
  397. WriteDisclaimer((*targetFolderBuildStreams)[pathUpTo]);
  398. // Add to main build file
  399. if (isFirst)
  400. {
  401. *mainBuildFile << relFilename << " ";
  402. GhsMultiGpj::WriteGpjTag(projType, mainBuildFile);
  403. }
  404. }
  405. void cmGlobalGhsMultiGenerator::AddFilesUpToPathAppendNextFile(
  406. std::map<std::string, cmGeneratedFileStream *> *targetFolderBuildStreams,
  407. std::string const &pathUpTo,
  408. std::vector<cmsys::String>::const_iterator splitPathI,
  409. std::vector<cmsys::String>::const_iterator end,
  410. GhsMultiGpj::Types const projType)
  411. {
  412. std::vector<cmsys::String>::const_iterator splitPathNextI = splitPathI + 1;
  413. if (end != splitPathNextI &&
  414. targetFolderBuildStreams->end() ==
  415. targetFolderBuildStreams->find(pathUpTo + "/" + *splitPathNextI))
  416. {
  417. std::string nextFilename(*splitPathNextI);
  418. nextFilename = GetFileNameFromPath(nextFilename);
  419. *(*targetFolderBuildStreams)[pathUpTo] << nextFilename << " ";
  420. GhsMultiGpj::WriteGpjTag(projType, (*targetFolderBuildStreams)[pathUpTo]);
  421. }
  422. }
  423. std::string
  424. cmGlobalGhsMultiGenerator::GetFileNameFromPath(std::string const &path)
  425. {
  426. std::string output(path);
  427. if (!path.empty())
  428. {
  429. cmSystemTools::ConvertToUnixSlashes(output);
  430. std::vector<cmsys::String> splitPath = cmSystemTools::SplitString(output);
  431. output += "/" + splitPath.back() + FILE_EXTENSION;
  432. }
  433. return output;
  434. }
  435. void cmGlobalGhsMultiGenerator::UpdateBuildFiles(
  436. std::vector<cmGeneratorTarget*> tgts)
  437. {
  438. for (std::vector<cmGeneratorTarget*>::iterator tgtsI = tgts.begin();
  439. tgtsI != tgts.end(); ++tgtsI)
  440. {
  441. const cmGeneratorTarget *tgt = *tgtsI;
  442. if (IsTgtForBuild(tgt->Target))
  443. {
  444. char const *rawFolderName = tgt->GetProperty("FOLDER");
  445. if (NULL == rawFolderName)
  446. {
  447. rawFolderName = "";
  448. }
  449. std::string folderName(rawFolderName);
  450. if (this->TargetFolderBuildStreams.end() ==
  451. this->TargetFolderBuildStreams.find(folderName))
  452. {
  453. this->AddFilesUpToPath(
  454. GetBuildFileStream(), &this->TargetFolderBuildStreams,
  455. this->GetCMakeInstance()->GetHomeOutputDirectory(), folderName,
  456. GhsMultiGpj::PROJECT);
  457. }
  458. std::vector<cmsys::String> splitPath = cmSystemTools::SplitString(
  459. cmGhsMultiTargetGenerator::GetRelBuildFileName(tgt->Target));
  460. std::string foldNameRelBuildFile(*(splitPath.end() - 2) + "/" +
  461. splitPath.back());
  462. *this->TargetFolderBuildStreams[folderName] << foldNameRelBuildFile
  463. << " ";
  464. GhsMultiGpj::WriteGpjTag(cmGhsMultiTargetGenerator::GetGpjTag(
  465. tgt),
  466. this->TargetFolderBuildStreams[folderName]);
  467. }
  468. }
  469. }
  470. bool cmGlobalGhsMultiGenerator::IsTgtForBuild(const cmTarget *tgt)
  471. {
  472. const std::string config =
  473. tgt->GetMakefile()->GetSafeDefinition("CMAKE_BUILD_TYPE");
  474. std::vector<cmSourceFile *> tgtSources;
  475. cmGeneratorTarget* gt = this->GetGeneratorTarget(tgt);
  476. gt->GetSourceFiles(tgtSources, config);
  477. bool tgtInBuild = true;
  478. char const *excludeFromAll = tgt->GetProperty("EXCLUDE_FROM_ALL");
  479. if (NULL != excludeFromAll && '1' == excludeFromAll[0] &&
  480. '\0' == excludeFromAll[1])
  481. {
  482. tgtInBuild = false;
  483. }
  484. return !tgtSources.empty() && tgtInBuild;
  485. }
  486. std::string cmGlobalGhsMultiGenerator::trimQuotes(std::string const &str)
  487. {
  488. std::string result;
  489. result.reserve(str.size());
  490. for (const char *ch = str.c_str(); *ch != '\0'; ++ch)
  491. {
  492. if (*ch != '"')
  493. {
  494. result += *ch;
  495. }
  496. }
  497. return result;
  498. }