cmTargetLinkLibrariesCommand.cxx 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file Copyright.txt or https://cmake.org/licensing for details. */
  3. #include "cmTargetLinkLibrariesCommand.h"
  4. #include <cstring>
  5. #include <sstream>
  6. #include "cmGeneratorExpression.h"
  7. #include "cmGlobalGenerator.h"
  8. #include "cmMakefile.h"
  9. #include "cmMessageType.h"
  10. #include "cmPolicies.h"
  11. #include "cmState.h"
  12. #include "cmStateTypes.h"
  13. #include "cmStringAlgorithms.h"
  14. #include "cmSystemTools.h"
  15. #include "cmTarget.h"
  16. #include "cmake.h"
  17. class cmExecutionStatus;
  18. const char* cmTargetLinkLibrariesCommand::LinkLibraryTypeNames[3] = {
  19. "general", "debug", "optimized"
  20. };
  21. // cmTargetLinkLibrariesCommand
  22. bool cmTargetLinkLibrariesCommand::InitialPass(
  23. std::vector<std::string> const& args, cmExecutionStatus&)
  24. {
  25. // Must have at least one argument.
  26. if (args.empty()) {
  27. this->SetError("called with incorrect number of arguments");
  28. return false;
  29. }
  30. // Alias targets cannot be on the LHS of this command.
  31. if (this->Makefile->IsAlias(args[0])) {
  32. this->SetError("can not be used on an ALIAS target.");
  33. return false;
  34. }
  35. // Lookup the target for which libraries are specified.
  36. this->Target =
  37. this->Makefile->GetCMakeInstance()->GetGlobalGenerator()->FindTarget(
  38. args[0]);
  39. if (!this->Target) {
  40. const std::vector<cmTarget*>& importedTargets =
  41. this->Makefile->GetOwnedImportedTargets();
  42. for (cmTarget* importedTarget : importedTargets) {
  43. if (importedTarget->GetName() == args[0]) {
  44. this->Target = importedTarget;
  45. break;
  46. }
  47. }
  48. }
  49. if (!this->Target) {
  50. MessageType t = MessageType::FATAL_ERROR; // fail by default
  51. std::ostringstream e;
  52. e << "Cannot specify link libraries for target \"" << args[0] << "\" "
  53. << "which is not built by this project.";
  54. // The bad target is the only argument. Check how policy CMP0016 is set,
  55. // and accept, warn or fail respectively:
  56. if (args.size() < 2) {
  57. switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0016)) {
  58. case cmPolicies::WARN:
  59. t = MessageType::AUTHOR_WARNING;
  60. // Print the warning.
  61. e << "\n"
  62. << "CMake does not support this but it used to work accidentally "
  63. << "and is being allowed for compatibility."
  64. << "\n"
  65. << cmPolicies::GetPolicyWarning(cmPolicies::CMP0016);
  66. break;
  67. case cmPolicies::OLD: // OLD behavior does not warn.
  68. t = MessageType::MESSAGE;
  69. break;
  70. case cmPolicies::REQUIRED_IF_USED:
  71. case cmPolicies::REQUIRED_ALWAYS:
  72. e << "\n" << cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0016);
  73. break;
  74. case cmPolicies::NEW: // NEW behavior prints the error.
  75. break;
  76. }
  77. }
  78. // Now actually print the message.
  79. switch (t) {
  80. case MessageType::AUTHOR_WARNING:
  81. this->Makefile->IssueMessage(MessageType::AUTHOR_WARNING, e.str());
  82. break;
  83. case MessageType::FATAL_ERROR:
  84. this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
  85. cmSystemTools::SetFatalErrorOccured();
  86. break;
  87. default:
  88. break;
  89. }
  90. return true;
  91. }
  92. // Having a UTILITY library on the LHS is a bug.
  93. if (this->Target->GetType() == cmStateEnums::UTILITY) {
  94. std::ostringstream e;
  95. const char* modal = nullptr;
  96. MessageType messageType = MessageType::AUTHOR_WARNING;
  97. switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0039)) {
  98. case cmPolicies::WARN:
  99. e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0039) << "\n";
  100. modal = "should";
  101. case cmPolicies::OLD:
  102. break;
  103. case cmPolicies::REQUIRED_ALWAYS:
  104. case cmPolicies::REQUIRED_IF_USED:
  105. case cmPolicies::NEW:
  106. modal = "must";
  107. messageType = MessageType::FATAL_ERROR;
  108. }
  109. if (modal) {
  110. e << "Utility target \"" << this->Target->GetName() << "\" " << modal
  111. << " not be used as the target of a target_link_libraries call.";
  112. this->Makefile->IssueMessage(messageType, e.str());
  113. if (messageType == MessageType::FATAL_ERROR) {
  114. return false;
  115. }
  116. }
  117. }
  118. // But we might not have any libs after variable expansion.
  119. if (args.size() < 2) {
  120. return true;
  121. }
  122. // Keep track of link configuration specifiers.
  123. cmTargetLinkLibraryType llt = GENERAL_LibraryType;
  124. bool haveLLT = false;
  125. // Start with primary linking and switch to link interface
  126. // specification if the keyword is encountered as the first argument.
  127. this->CurrentProcessingState = ProcessingLinkLibraries;
  128. // Add libraries, note that there is an optional prefix
  129. // of debug and optimized that can be used.
  130. for (unsigned int i = 1; i < args.size(); ++i) {
  131. if (args[i] == "LINK_INTERFACE_LIBRARIES") {
  132. this->CurrentProcessingState = ProcessingPlainLinkInterface;
  133. if (i != 1) {
  134. this->Makefile->IssueMessage(
  135. MessageType::FATAL_ERROR,
  136. "The LINK_INTERFACE_LIBRARIES option must appear as the second "
  137. "argument, just after the target name.");
  138. return true;
  139. }
  140. } else if (args[i] == "INTERFACE") {
  141. if (i != 1 &&
  142. this->CurrentProcessingState != ProcessingKeywordPrivateInterface &&
  143. this->CurrentProcessingState != ProcessingKeywordPublicInterface &&
  144. this->CurrentProcessingState != ProcessingKeywordLinkInterface) {
  145. this->Makefile->IssueMessage(
  146. MessageType::FATAL_ERROR,
  147. "The INTERFACE, PUBLIC or PRIVATE option must appear as the second "
  148. "argument, just after the target name.");
  149. return true;
  150. }
  151. this->CurrentProcessingState = ProcessingKeywordLinkInterface;
  152. } else if (args[i] == "LINK_PUBLIC") {
  153. if (i != 1 &&
  154. this->CurrentProcessingState != ProcessingPlainPrivateInterface &&
  155. this->CurrentProcessingState != ProcessingPlainPublicInterface) {
  156. this->Makefile->IssueMessage(
  157. MessageType::FATAL_ERROR,
  158. "The LINK_PUBLIC or LINK_PRIVATE option must appear as the second "
  159. "argument, just after the target name.");
  160. return true;
  161. }
  162. this->CurrentProcessingState = ProcessingPlainPublicInterface;
  163. } else if (args[i] == "PUBLIC") {
  164. if (i != 1 &&
  165. this->CurrentProcessingState != ProcessingKeywordPrivateInterface &&
  166. this->CurrentProcessingState != ProcessingKeywordPublicInterface &&
  167. this->CurrentProcessingState != ProcessingKeywordLinkInterface) {
  168. this->Makefile->IssueMessage(
  169. MessageType::FATAL_ERROR,
  170. "The INTERFACE, PUBLIC or PRIVATE option must appear as the second "
  171. "argument, just after the target name.");
  172. return true;
  173. }
  174. this->CurrentProcessingState = ProcessingKeywordPublicInterface;
  175. } else if (args[i] == "LINK_PRIVATE") {
  176. if (i != 1 &&
  177. this->CurrentProcessingState != ProcessingPlainPublicInterface &&
  178. this->CurrentProcessingState != ProcessingPlainPrivateInterface) {
  179. this->Makefile->IssueMessage(
  180. MessageType::FATAL_ERROR,
  181. "The LINK_PUBLIC or LINK_PRIVATE option must appear as the second "
  182. "argument, just after the target name.");
  183. return true;
  184. }
  185. this->CurrentProcessingState = ProcessingPlainPrivateInterface;
  186. } else if (args[i] == "PRIVATE") {
  187. if (i != 1 &&
  188. this->CurrentProcessingState != ProcessingKeywordPrivateInterface &&
  189. this->CurrentProcessingState != ProcessingKeywordPublicInterface &&
  190. this->CurrentProcessingState != ProcessingKeywordLinkInterface) {
  191. this->Makefile->IssueMessage(
  192. MessageType::FATAL_ERROR,
  193. "The INTERFACE, PUBLIC or PRIVATE option must appear as the second "
  194. "argument, just after the target name.");
  195. return true;
  196. }
  197. this->CurrentProcessingState = ProcessingKeywordPrivateInterface;
  198. } else if (args[i] == "debug") {
  199. if (haveLLT) {
  200. this->LinkLibraryTypeSpecifierWarning(llt, DEBUG_LibraryType);
  201. }
  202. llt = DEBUG_LibraryType;
  203. haveLLT = true;
  204. } else if (args[i] == "optimized") {
  205. if (haveLLT) {
  206. this->LinkLibraryTypeSpecifierWarning(llt, OPTIMIZED_LibraryType);
  207. }
  208. llt = OPTIMIZED_LibraryType;
  209. haveLLT = true;
  210. } else if (args[i] == "general") {
  211. if (haveLLT) {
  212. this->LinkLibraryTypeSpecifierWarning(llt, GENERAL_LibraryType);
  213. }
  214. llt = GENERAL_LibraryType;
  215. haveLLT = true;
  216. } else if (haveLLT) {
  217. // The link type was specified by the previous argument.
  218. haveLLT = false;
  219. if (!this->HandleLibrary(args[i], llt)) {
  220. return false;
  221. }
  222. } else {
  223. // Lookup old-style cache entry if type is unspecified. So if you
  224. // do a target_link_libraries(foo optimized bar) it will stay optimized
  225. // and not use the lookup. As there may be the case where someone has
  226. // specified that a library is both debug and optimized. (this check is
  227. // only there for backwards compatibility when mixing projects built
  228. // with old versions of CMake and new)
  229. llt = GENERAL_LibraryType;
  230. std::string linkType = cmStrCat(args[0], "_LINK_TYPE");
  231. const char* linkTypeString = this->Makefile->GetDefinition(linkType);
  232. if (linkTypeString) {
  233. if (strcmp(linkTypeString, "debug") == 0) {
  234. llt = DEBUG_LibraryType;
  235. }
  236. if (strcmp(linkTypeString, "optimized") == 0) {
  237. llt = OPTIMIZED_LibraryType;
  238. }
  239. }
  240. if (!this->HandleLibrary(args[i], llt)) {
  241. return false;
  242. }
  243. }
  244. }
  245. // Make sure the last argument was not a library type specifier.
  246. if (haveLLT) {
  247. this->Makefile->IssueMessage(
  248. MessageType::FATAL_ERROR,
  249. cmStrCat("The \"",
  250. cmTargetLinkLibrariesCommand::LinkLibraryTypeNames[llt],
  251. "\" argument must be followed by a library."));
  252. cmSystemTools::SetFatalErrorOccured();
  253. }
  254. const cmPolicies::PolicyStatus policy22Status =
  255. this->Target->GetPolicyStatusCMP0022();
  256. // If any of the LINK_ options were given, make sure the
  257. // LINK_INTERFACE_LIBRARIES target property exists.
  258. // Use of any of the new keywords implies awareness of
  259. // this property. And if no libraries are named, it should
  260. // result in an empty link interface.
  261. if ((policy22Status == cmPolicies::OLD ||
  262. policy22Status == cmPolicies::WARN) &&
  263. this->CurrentProcessingState != ProcessingLinkLibraries &&
  264. !this->Target->GetProperty("LINK_INTERFACE_LIBRARIES")) {
  265. this->Target->SetProperty("LINK_INTERFACE_LIBRARIES", "");
  266. }
  267. return true;
  268. }
  269. void cmTargetLinkLibrariesCommand::LinkLibraryTypeSpecifierWarning(int left,
  270. int right)
  271. {
  272. this->Makefile->IssueMessage(
  273. MessageType::AUTHOR_WARNING,
  274. cmStrCat(
  275. "Link library type specifier \"",
  276. cmTargetLinkLibrariesCommand::LinkLibraryTypeNames[left],
  277. "\" is followed by specifier \"",
  278. cmTargetLinkLibrariesCommand::LinkLibraryTypeNames[right],
  279. "\" instead of a library name. The first specifier will be ignored."));
  280. }
  281. bool cmTargetLinkLibrariesCommand::HandleLibrary(const std::string& lib,
  282. cmTargetLinkLibraryType llt)
  283. {
  284. if (this->Target->GetType() == cmStateEnums::INTERFACE_LIBRARY &&
  285. this->CurrentProcessingState != ProcessingKeywordLinkInterface) {
  286. this->Makefile->IssueMessage(
  287. MessageType::FATAL_ERROR,
  288. "INTERFACE library can only be used with the INTERFACE keyword of "
  289. "target_link_libraries");
  290. return false;
  291. }
  292. if (this->Target->IsImported() &&
  293. this->CurrentProcessingState != ProcessingKeywordLinkInterface) {
  294. this->Makefile->IssueMessage(
  295. MessageType::FATAL_ERROR,
  296. "IMPORTED library can only be used with the INTERFACE keyword of "
  297. "target_link_libraries");
  298. return false;
  299. }
  300. cmTarget::TLLSignature sig =
  301. (this->CurrentProcessingState == ProcessingPlainPrivateInterface ||
  302. this->CurrentProcessingState == ProcessingPlainPublicInterface ||
  303. this->CurrentProcessingState == ProcessingKeywordPrivateInterface ||
  304. this->CurrentProcessingState == ProcessingKeywordPublicInterface ||
  305. this->CurrentProcessingState == ProcessingKeywordLinkInterface)
  306. ? cmTarget::KeywordTLLSignature
  307. : cmTarget::PlainTLLSignature;
  308. if (!this->Target->PushTLLCommandTrace(
  309. sig, this->Makefile->GetExecutionContext())) {
  310. std::ostringstream e;
  311. const char* modal = nullptr;
  312. MessageType messageType = MessageType::AUTHOR_WARNING;
  313. switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0023)) {
  314. case cmPolicies::WARN:
  315. e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0023) << "\n";
  316. modal = "should";
  317. case cmPolicies::OLD:
  318. break;
  319. case cmPolicies::REQUIRED_ALWAYS:
  320. case cmPolicies::REQUIRED_IF_USED:
  321. case cmPolicies::NEW:
  322. modal = "must";
  323. messageType = MessageType::FATAL_ERROR;
  324. }
  325. if (modal) {
  326. // If the sig is a keyword form and there is a conflict, the existing
  327. // form must be the plain form.
  328. const char* existingSig =
  329. (sig == cmTarget::KeywordTLLSignature ? "plain" : "keyword");
  330. e << "The " << existingSig
  331. << " signature for target_link_libraries has "
  332. "already been used with the target \""
  333. << this->Target->GetName()
  334. << "\". All uses of target_link_libraries with a target " << modal
  335. << " be either all-keyword or all-plain.\n";
  336. this->Target->GetTllSignatureTraces(e,
  337. sig == cmTarget::KeywordTLLSignature
  338. ? cmTarget::PlainTLLSignature
  339. : cmTarget::KeywordTLLSignature);
  340. this->Makefile->IssueMessage(messageType, e.str());
  341. if (messageType == MessageType::FATAL_ERROR) {
  342. return false;
  343. }
  344. }
  345. }
  346. bool warnRemoteInterface = false;
  347. bool rejectRemoteLinking = false;
  348. bool encodeRemoteReference = false;
  349. if (this->Makefile != this->Target->GetMakefile()) {
  350. // The LHS target was created in another directory.
  351. switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0079)) {
  352. case cmPolicies::WARN:
  353. warnRemoteInterface = true;
  354. CM_FALLTHROUGH;
  355. case cmPolicies::OLD:
  356. rejectRemoteLinking = true;
  357. break;
  358. case cmPolicies::REQUIRED_ALWAYS:
  359. case cmPolicies::REQUIRED_IF_USED:
  360. case cmPolicies::NEW:
  361. encodeRemoteReference = true;
  362. break;
  363. }
  364. }
  365. std::string libRef;
  366. if (encodeRemoteReference && !cmSystemTools::FileIsFullPath(lib)) {
  367. // This is a library name added by a caller that is not in the
  368. // same directory as the target was created. Add a suffix to
  369. // the name to tell ResolveLinkItem to look up the name in the
  370. // caller's directory.
  371. cmDirectoryId const dirId = this->Makefile->GetDirectoryId();
  372. libRef = lib + CMAKE_DIRECTORY_ID_SEP + dirId.String;
  373. } else {
  374. // This is an absolute path or a library name added by a caller
  375. // in the same directory as the target was created. We can use
  376. // the original name directly.
  377. libRef = lib;
  378. }
  379. // Handle normal case where the command was called with another keyword than
  380. // INTERFACE / LINK_INTERFACE_LIBRARIES or none at all. (The "LINK_LIBRARIES"
  381. // property of the target on the LHS shall be populated.)
  382. if (this->CurrentProcessingState != ProcessingKeywordLinkInterface &&
  383. this->CurrentProcessingState != ProcessingPlainLinkInterface) {
  384. if (rejectRemoteLinking) {
  385. this->Makefile->IssueMessage(
  386. MessageType::FATAL_ERROR,
  387. cmStrCat("Attempt to add link library \"", lib, "\" to target \"",
  388. this->Target->GetName(),
  389. "\" which is not built in this "
  390. "directory.\nThis is allowed only when policy CMP0079 "
  391. "is set to NEW."));
  392. return false;
  393. }
  394. cmTarget* tgt = this->Makefile->GetGlobalGenerator()->FindTarget(lib);
  395. if (tgt && (tgt->GetType() != cmStateEnums::STATIC_LIBRARY) &&
  396. (tgt->GetType() != cmStateEnums::SHARED_LIBRARY) &&
  397. (tgt->GetType() != cmStateEnums::UNKNOWN_LIBRARY) &&
  398. (tgt->GetType() != cmStateEnums::OBJECT_LIBRARY) &&
  399. (tgt->GetType() != cmStateEnums::INTERFACE_LIBRARY) &&
  400. !tgt->IsExecutableWithExports()) {
  401. this->Makefile->IssueMessage(
  402. MessageType::FATAL_ERROR,
  403. cmStrCat(
  404. "Target \"", lib, "\" of type ",
  405. cmState::GetTargetTypeName(tgt->GetType()),
  406. " may not be linked into another target. One may link only to "
  407. "INTERFACE, OBJECT, STATIC or SHARED libraries, or to ",
  408. "executables with the ENABLE_EXPORTS property set."));
  409. }
  410. this->Target->AddLinkLibrary(*this->Makefile, lib, libRef, llt);
  411. }
  412. if (warnRemoteInterface) {
  413. this->Makefile->IssueMessage(
  414. MessageType::AUTHOR_WARNING,
  415. cmStrCat(
  416. cmPolicies::GetPolicyWarning(cmPolicies::CMP0079), "\nTarget\n ",
  417. this->Target->GetName(),
  418. "\nis not created in this "
  419. "directory. For compatibility with older versions of CMake, link "
  420. "library\n ",
  421. lib,
  422. "\nwill be looked up in the directory in which "
  423. "the target was created rather than in this calling directory."));
  424. }
  425. // Handle (additional) case where the command was called with PRIVATE /
  426. // LINK_PRIVATE and stop its processing. (The "INTERFACE_LINK_LIBRARIES"
  427. // property of the target on the LHS shall only be populated if it is a
  428. // STATIC library.)
  429. if (this->CurrentProcessingState == ProcessingKeywordPrivateInterface ||
  430. this->CurrentProcessingState == ProcessingPlainPrivateInterface) {
  431. if (this->Target->GetType() == cmStateEnums::STATIC_LIBRARY ||
  432. this->Target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
  433. std::string configLib =
  434. this->Target->GetDebugGeneratorExpressions(libRef, llt);
  435. if (cmGeneratorExpression::IsValidTargetName(lib) ||
  436. cmGeneratorExpression::Find(lib) != std::string::npos) {
  437. configLib = "$<LINK_ONLY:" + configLib + ">";
  438. }
  439. this->Target->AppendProperty("INTERFACE_LINK_LIBRARIES",
  440. configLib.c_str());
  441. }
  442. return true;
  443. }
  444. // Handle general case where the command was called with another keyword than
  445. // PRIVATE / LINK_PRIVATE or none at all. (The "INTERFACE_LINK_LIBRARIES"
  446. // property of the target on the LHS shall be populated.)
  447. this->Target->AppendProperty(
  448. "INTERFACE_LINK_LIBRARIES",
  449. this->Target->GetDebugGeneratorExpressions(libRef, llt).c_str());
  450. // Stop processing if called without any keyword.
  451. if (this->CurrentProcessingState == ProcessingLinkLibraries) {
  452. return true;
  453. }
  454. // Stop processing if policy CMP0022 is set to NEW.
  455. const cmPolicies::PolicyStatus policy22Status =
  456. this->Target->GetPolicyStatusCMP0022();
  457. if (policy22Status != cmPolicies::OLD &&
  458. policy22Status != cmPolicies::WARN) {
  459. return true;
  460. }
  461. // Stop processing if called with an INTERFACE library on the LHS.
  462. if (this->Target->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
  463. return true;
  464. }
  465. // Handle (additional) backward-compatibility case where the command was
  466. // called with PUBLIC / INTERFACE / LINK_PUBLIC / LINK_INTERFACE_LIBRARIES.
  467. // (The policy CMP0022 is not set to NEW.)
  468. {
  469. // Get the list of configurations considered to be DEBUG.
  470. std::vector<std::string> debugConfigs =
  471. this->Makefile->GetCMakeInstance()->GetDebugConfigs();
  472. std::string prop;
  473. // Include this library in the link interface for the target.
  474. if (llt == DEBUG_LibraryType || llt == GENERAL_LibraryType) {
  475. // Put in the DEBUG configuration interfaces.
  476. for (std::string const& dc : debugConfigs) {
  477. prop = cmStrCat("LINK_INTERFACE_LIBRARIES_", dc);
  478. this->Target->AppendProperty(prop, libRef.c_str());
  479. }
  480. }
  481. if (llt == OPTIMIZED_LibraryType || llt == GENERAL_LibraryType) {
  482. // Put in the non-DEBUG configuration interfaces.
  483. this->Target->AppendProperty("LINK_INTERFACE_LIBRARIES", libRef.c_str());
  484. // Make sure the DEBUG configuration interfaces exist so that the
  485. // general one will not be used as a fall-back.
  486. for (std::string const& dc : debugConfigs) {
  487. prop = cmStrCat("LINK_INTERFACE_LIBRARIES_", dc);
  488. if (!this->Target->GetProperty(prop)) {
  489. this->Target->SetProperty(prop, "");
  490. }
  491. }
  492. }
  493. }
  494. return true;
  495. }