1 // Copyright (c) 2024 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 #include "gn/ohos_components.h"
5
6 #include <cstring>
7 #include <iostream>
8 #include <map>
9
10 #include "base/files/file_path.h"
11 #include "base/files/file_util.h"
12 #include "base/json/json_reader.h"
13 #include "base/values.h"
14
15 #include "gn/err.h"
16 #include "gn/filesystem_utils.h"
17 #include "gn/innerapis_publicinfo_generator.h"
18 #include "gn/ohos_components_checker.h"
19 #include "gn/ohos_components_impl.h"
20 #include "gn/ohos_components_mapping.h"
21
22 /**
23 * Ohos Component API
24 *
25 * Each component belongs to one subsystem.
26 * Each component has a source path.
27 * Each component has zero or more innerapis
28 */
29
30 static const std::string EMPTY_INNERAPI;
31
32 static const int PATH_PREFIX_LEN = 2;
33
34 OhosComponent::OhosComponent() = default;
35
GetPath(const char * path)36 static std::string GetPath(const char *path)
37 {
38 std::string process_path;
39 if (strncmp(path, "//", PATH_PREFIX_LEN) == 0) {
40 process_path = std::string(path);
41 } else {
42 process_path = "//" + std::string(path);
43 }
44 return process_path;
45 }
46
OhosComponent(const char * name,const char * subsystem,const char * path,const std::vector<std::string> & modulePath,bool special_parts_switch)47 OhosComponent::OhosComponent(const char *name, const char *subsystem, const char *path,
48 const std::vector<std::string> &modulePath, bool special_parts_switch)
49 {
50 name_ = std::string(name);
51 subsystem_ = std::string(subsystem);
52 path_ = GetPath(path);
53 special_parts_switch_ = special_parts_switch;
54
55 auto result = std::find(modulePath.begin(), modulePath.end(), std::string(path));
56 if (result == modulePath.end()) {
57 module_path_.push_back(path_);
58 } else {
59 for (const auto &module_path : modulePath) {
60 module_path_.push_back(GetPath(module_path.c_str()));
61 }
62 }
63 }
64
addInnerApi(const std::string & name,const std::string & label)65 void OhosComponent::addInnerApi(const std::string &name, const std::string &label)
66 {
67 std::string la = label;
68 size_t pos = label.find(":");
69 if (pos != std::string::npos) {
70 if ((label[pos - 1]) == '/') { // some are like this : "//components/foo/libfoo/:libfoo"
71 unsigned long indexToRemove = pos - 1;
72 if (indexToRemove >= 0 && indexToRemove <= la.length()) {
73 la.erase(la.begin() + indexToRemove);
74 }
75 }
76 }
77 innerapi_names_[name] = la;
78 innerapi_labels_[la] = name;
79 }
80
81
getInnerApi(const std::string & innerapi) const82 const std::string &OhosComponent::getInnerApi(const std::string &innerapi) const
83 {
84 if (auto res = innerapi_names_.find(innerapi); res != innerapi_names_.end()) {
85 return res->second;
86 }
87 return EMPTY_INNERAPI;
88 }
89
isInnerApi(const std::string & label) const90 bool OhosComponent::isInnerApi(const std::string &label) const
91 {
92 if (auto res = innerapi_labels_.find(label); res != innerapi_labels_.end()) {
93 return true;
94 }
95 return false;
96 }
97
addInnerApiVisibility(const std::string & name,const std::vector<base::Value> & list)98 void OhosComponent::addInnerApiVisibility(const std::string &name, const std::vector<base::Value> &list)
99 {
100 for (const base::Value &visibility : list) {
101 innerapi_visibility_[innerapi_names_[name]].push_back(visibility.GetString());
102 }
103 }
104
getInnerApiVisibility(const std::string & label) const105 const std::vector<std::string> OhosComponent::getInnerApiVisibility(const std::string &label) const
106 {
107 if (auto res = innerapi_visibility_.find(label); res != innerapi_visibility_.end()) {
108 return res->second;
109 }
110 return {};
111 }
112
113 /**
114 * Ohos Component Implimentation API
115 */
116 OhosComponentsImpl::OhosComponentsImpl() = default;
117
ReadBuildConfigFile(const std::string & build_dir,const char * subfile,std::string & content)118 bool OhosComponentsImpl::ReadBuildConfigFile(const std::string &build_dir, const char *subfile, std::string &content)
119 {
120 std::string path = build_dir;
121 path += "/build_configs/";
122 path += subfile;
123 return base::ReadFileToString(base::FilePath(path), &content);
124 }
125
GetComponentPath(std::string & content)126 static bool GetComponentPath(std::string &content)
127 {
128 std::string whiteListPath = "out/products_ext/component_path_whitelist.json";
129 return base::ReadFileToString(base::FilePath(whiteListPath), &content);
130 }
131
GetModulePath(const std::string & path,base::ListValue * list)132 static std::vector<std::string> GetModulePath(const std::string &path, base::ListValue *list)
133 {
134 std::vector<std::string> module_path;
135 for (const base::Value &value : list->GetList()) {
136 const base::Value *component_path = value.FindKey("component_path");
137 if (!component_path) {
138 continue;
139 }
140 if (component_path->GetString() == path) {
141 for (const base::Value &item : value.FindKey("module_path")->GetList()) {
142 module_path.push_back(item.GetString());
143 }
144 return module_path;
145 }
146 }
147 return module_path;
148 }
149
LoadComponentInfo(const std::string & components_content,bool special_parts_switch,std::string & err_msg_out)150 bool OhosComponentsImpl::LoadComponentInfo(const std::string &components_content, bool special_parts_switch,
151 std::string &err_msg_out)
152 {
153 const base::DictionaryValue *components_dict;
154 std::unique_ptr<base::Value> components_value = base::JSONReader::ReadAndReturnError(components_content,
155 base::JSONParserOptions::JSON_PARSE_RFC, nullptr, &err_msg_out, nullptr, nullptr);
156 if (!components_value) {
157 return false;
158 }
159 if (!components_value->GetAsDictionary(&components_dict)) {
160 return false;
161 }
162
163 std::string components_path_content;
164 base::ListValue *components_path_list;
165 bool is_read_path = false;
166 std::unique_ptr<base::Value> components_path_value;
167 if (special_parts_switch && GetComponentPath(components_path_content)) {
168 components_path_value = base::JSONReader::ReadAndReturnError(components_path_content,
169 base::JSONParserOptions::JSON_PARSE_RFC, nullptr, &err_msg_out, nullptr, nullptr);
170 if (components_path_value) {
171 is_read_path = components_path_value->GetAsList(&components_path_list);
172 }
173 }
174
175 for (const auto com : components_dict->DictItems()) {
176 const base::Value *subsystem = com.second.FindKey("subsystem");
177 const base::Value *path = com.second.FindKey("path");
178 if (!subsystem || !path) {
179 continue;
180 }
181
182 std::vector<std::string> module_path;
183 if (special_parts_switch && is_read_path) {
184 module_path = GetModulePath(path-> GetString(), components_path_list);
185 }
186
187 components_[com.first] =
188 new OhosComponent(com.first.c_str(), subsystem->GetString().c_str(), path->GetString().c_str(),
189 module_path, special_parts_switch);
190 const base::Value *innerapis = com.second.FindKey("innerapis");
191 if (!innerapis) {
192 continue;
193 }
194 LoadInnerApi(com.first, innerapis->GetList());
195 }
196 setupComponentsTree();
197 return true;
198 }
199
findChildByPath(const struct OhosComponentTree * current,const char * path,size_t len)200 const struct OhosComponentTree *OhosComponentsImpl::findChildByPath(const struct OhosComponentTree *current,
201 const char *path, size_t len)
202 {
203 if (current->child == nullptr) {
204 return nullptr;
205 }
206 const struct OhosComponentTree *item = current->child;
207 while (item != nullptr) {
208 if (strncmp(item->dirName, path, len) == 0) {
209 // Exactly matching
210 if (item->dirName[len] == '\0') {
211 return item;
212 }
213 }
214 item = item->next;
215 }
216
217 return nullptr;
218 }
219
matchComponentByLabel(const char * label)220 const OhosComponent *OhosComponentsImpl::matchComponentByLabel(const char *label)
221 {
222 const struct OhosComponentTree *child;
223 const struct OhosComponentTree *previous = pathTree;
224 const struct OhosComponentTree *current = pathTree;
225
226 if (!label) {
227 return nullptr;
228 }
229 // Skip leading //
230 if (strncmp(label, "//", PATH_PREFIX_LEN) == 0) {
231 label += PATH_PREFIX_LEN;
232 }
233
234 size_t len;
235 const char *sep;
236 while (label[0] != '\0') {
237 // Get next path seperator
238 sep = strchr(label, '/');
239 if (sep) {
240 len = sep - label;
241 } else {
242 // Check if it is a label with target name
243 sep = strchr(label, ':');
244 if (sep) {
245 len = sep - label;
246 } else {
247 len = strlen(label);
248 }
249 }
250
251 // Match with children
252 child = findChildByPath(current, label, len);
253 if (child == nullptr) {
254 if (current->child != nullptr && previous->component != nullptr &&
255 previous->component->specialPartsSwitch()) {
256 return previous->component;
257 } else {
258 break;
259 }
260 }
261
262 // No children, return current matched item
263 if (child->child == nullptr) {
264 return child->component;
265 }
266
267 label += len;
268 // Finish matching if target name started
269 if (label[0] == ':') {
270 return child->component;
271 }
272
273 // Save previous part target
274 if (child->component != nullptr) {
275 previous = child;
276 }
277
278 // Match with child again
279 current = child;
280
281 // Skip leading seperator
282 if (label[0] == '/') {
283 label += 1;
284 }
285 }
286
287 return nullptr;
288 }
289
addComponentToTree(struct OhosComponentTree * current,OhosComponent * component)290 void OhosComponentsImpl::addComponentToTree(struct OhosComponentTree *current, OhosComponent *component)
291 {
292 std::vector<std::string> module_path = component->modulePath();
293 struct OhosComponentTree *origin = current;
294 for (const auto &part_path : module_path) {
295 size_t len;
296 const char *path = part_path.c_str() + PATH_PREFIX_LEN;
297 const char *sep;
298 current = origin;
299
300 while (path[0] != '\0') {
301 sep = strchr(path, '/');
302 if (sep) {
303 len = sep - path;
304 } else {
305 len = strlen(path);
306 }
307
308 // Check if node already exists
309 struct OhosComponentTree *child = (struct OhosComponentTree *)findChildByPath(current, path, len);
310 if (!child) {
311 // Add intermediate node
312 child = new struct OhosComponentTree(path, len, nullptr);
313 child->next = current->child;
314 current->child = child;
315 }
316
317 // End of path detected, setup component pointer
318 path = path + len;
319 if (path[0] == '\0') {
320 child->component = component;
321 break;
322 }
323
324 // Continue to add next part
325 path += 1;
326 current = child;
327 }
328 }
329 }
330
setupComponentsTree()331 void OhosComponentsImpl::setupComponentsTree()
332 {
333 pathTree = new struct OhosComponentTree("//", nullptr);
334
335 std::map<std::string, OhosComponent *>::iterator it;
336 for (it = components_.begin(); it != components_.end(); it++) {
337 addComponentToTree(pathTree, it->second);
338 }
339 }
340
LoadInnerApi(const std::string & component_name,const std::vector<base::Value> & innerapis)341 void OhosComponentsImpl::LoadInnerApi(const std::string &component_name, const std::vector<base::Value> &innerapis)
342 {
343 OhosComponent *component = (OhosComponent *)GetComponentByName(component_name);
344 if (!component) {
345 return;
346 }
347 for (const base::Value &kv : innerapis) {
348 const base::Value *label = kv.FindKey("label");
349 const base::Value *name = kv.FindKey("name");
350
351 if (!label || !name) {
352 continue;
353 }
354 component->addInnerApi(name->GetString(), label->GetString());
355 const base::Value *visibility = kv.FindKey("visibility");
356 if (!visibility) {
357 continue;
358 }
359 component->addInnerApiVisibility(name->GetString(), visibility->GetList());
360 }
361 }
362
LoadOverrideMap(const std::string & override_map)363 void OhosComponentsImpl::LoadOverrideMap(const std::string &override_map)
364 {
365 const base::DictionaryValue *override_dict;
366 std::unique_ptr<base::Value> override_value = base::JSONReader::ReadAndReturnError(override_map,
367 base::JSONParserOptions::JSON_PARSE_RFC, nullptr, nullptr, nullptr, nullptr);
368 if (!override_value) {
369 return;
370 }
371 if (!override_value->GetAsDictionary(&override_dict)) {
372 return;
373 }
374
375 for (const auto com : override_dict->DictItems()) {
376 override_map_[com.first] = com.second.GetString();
377 }
378 return;
379 }
380
LoadToolchain(const Value * product)381 void OhosComponentsImpl::LoadToolchain(const Value *product)
382 {
383 if (!product) {
384 return;
385 }
386 std::string path = "out/preloader/" + product->string_value() + "/build_config.json";
387 std::string content;
388 if (!base::ReadFileToString(base::FilePath(path), &content)) {
389 return;
390 }
391
392 const base::DictionaryValue *content_dict;
393 std::unique_ptr<base::Value> content_value = base::JSONReader::ReadAndReturnError(content,
394 base::JSONParserOptions::JSON_PARSE_RFC, nullptr, nullptr, nullptr, nullptr);
395 if (!content_value) {
396 return;
397 }
398 if (!content_value->GetAsDictionary(&content_dict)) {
399 return;
400 }
401
402 for (const auto com : content_dict->DictItems()) {
403 if (com.first == "product_toolchain_label") {
404 toolchain_ = com.second.GetString();
405 break;
406 }
407 }
408 return;
409 }
410
LoadOhosComponents(const std::string & build_dir,const Value * enable,const Value * indep,const Value * product,bool special_parts_switch,Err * err)411 bool OhosComponentsImpl::LoadOhosComponents(const std::string &build_dir, const Value *enable,
412 const Value *indep, const Value *product, bool special_parts_switch, Err *err)
413 {
414 const char *components_file = "parts_info/components.json";
415 std::string components_content;
416 if (!ReadBuildConfigFile(build_dir, components_file, components_content)) {
417 *err = Err(*enable, "Your .gn file has enabled \"ohos_components_support\", but "
418 "OpenHarmony build config file (" +
419 std::string(components_file) + ") does not exists.\n");
420 return false;
421 }
422
423 std::string override_map;
424 if (ReadBuildConfigFile(build_dir, "component_override_map.json", override_map)) {
425 LoadOverrideMap(override_map);
426 }
427
428 std::string err_msg_out;
429 if (!LoadComponentInfo(components_content, special_parts_switch, err_msg_out)) {
430 *err = Err(*enable, "Your .gn file has enabled \"ohos_components_support\", but "
431 "OpenHarmony build config file parsing failed:\n" +
432 err_msg_out + "\n");
433 return false;
434 }
435 if (indep && indep->boolean_value()) {
436 is_indep_compiler_enable_ = true;
437 }
438 LoadToolchain(product);
439 return true;
440 }
441
GetComponentByName(const std::string & component_name) const442 const OhosComponent *OhosComponentsImpl::GetComponentByName(const std::string &component_name) const
443 {
444 if (auto res = components_.find(component_name); res != components_.end()) {
445 return res->second;
446 }
447 return nullptr;
448 }
449
GetWholeArchiveFlag(std::string str_val,int & whole_status)450 static size_t GetWholeArchiveFlag(std::string str_val, int &whole_status)
451 {
452 size_t sep_whole = str_val.find("(--whole-archive)");
453 if (sep_whole != std::string::npos) {
454 whole_status = 1;
455 } else {
456 sep_whole = str_val.find("(--no-whole-archive)");
457 if (sep_whole != std::string::npos) {
458 whole_status = 0;
459 } else {
460 whole_status = -1;
461 }
462 }
463 return sep_whole;
464 }
465
GetPrivateDepsLabel(const Value & dep,std::string & label,const Label & current_toolchain,int & whole_status,Err * err) const466 bool OhosComponentsImpl::GetPrivateDepsLabel(const Value &dep, std::string &label,
467 const Label& current_toolchain, int &whole_status, Err *err) const
468 {
469 std::string str_val = dep.string_value();
470 size_t sep_whole = GetWholeArchiveFlag(str_val, whole_status);
471
472 if (sep_whole != std::string::npos) {
473 label = str_val.substr(0, sep_whole);
474 } else {
475 label = str_val;
476 }
477 std::string current_toolchain_str = current_toolchain.GetUserVisibleName(false);
478 size_t tool_sep = label.find("(");
479 if (tool_sep == std::string::npos && GetTargetToolchain() != current_toolchain_str) {
480 label += "(" + current_toolchain_str + ")";
481 }
482 if (label == EMPTY_INNERAPI) {
483 *err = Err(dep,
484 "Deps label: (" + dep.string_value() + ") format error.");
485 return false;
486 }
487 return true;
488 }
489
GetExternalDepsLabel(const Value & external_dep,std::string & label,const Label & current_toolchain,int & whole_status,Err * err) const490 bool OhosComponentsImpl::GetExternalDepsLabel(const Value &external_dep, std::string &label,
491 const Label& current_toolchain, int &whole_status, Err *err) const
492 {
493 std::string str_val = external_dep.string_value();
494 size_t sep = str_val.find(":");
495 if (sep == std::string::npos) {
496 *err = Err(external_dep, "OHOS component external_deps format error: (" + external_dep.string_value() +
497 "),"
498 "it should be a string like \"component_name:innerapi_name\".");
499 return false;
500 }
501 std::string component_name = str_val.substr(0, sep);
502 for (const auto& pair : override_map_) {
503 if (pair.first == component_name) {
504 component_name = pair.second;
505 break;
506 }
507 }
508 const OhosComponent *component = GetComponentByName(component_name);
509 if (component == nullptr) {
510 *err = Err(external_dep, "OHOS component : (" + component_name + ") not found.");
511 return false;
512 }
513
514 std::string innerapi_name;
515 std::string tool_chain = "";
516 size_t sep_whole = GetWholeArchiveFlag(str_val, whole_status);
517 if (sep_whole != std::string::npos) {
518 innerapi_name = str_val.substr(sep + 1, sep_whole - sep - 1);
519 } else {
520 innerapi_name = str_val.substr(sep + 1);
521 size_t tool_sep = innerapi_name.find("(");
522 if (tool_sep != std::string::npos) {
523 tool_chain = innerapi_name.substr(tool_sep);
524 innerapi_name = innerapi_name.substr(0, tool_sep);
525 }
526 }
527
528 std::string current_toolchain_str = current_toolchain.GetUserVisibleName(false);
529 if (tool_chain == "" && GetTargetToolchain() != current_toolchain_str) {
530 tool_chain = "(" + current_toolchain_str + ")";
531 }
532 if (isOhosIndepCompilerEnable()) {
533 label = component->getInnerApi(innerapi_name + tool_chain);
534 if (label == EMPTY_INNERAPI) {
535 label = component->getInnerApi(innerapi_name) + tool_chain;
536 }
537 } else {
538 label = component->getInnerApi(innerapi_name) + tool_chain;
539 }
540
541 if (label == EMPTY_INNERAPI) {
542 *err = Err(external_dep,
543 "OHOS innerapi: (" + innerapi_name + ") not found for component (" + component_name + ").");
544 return false;
545 }
546 return true;
547 }
548
GetSubsystemName(const Value & component_name,std::string & subsystem_name,Err * err) const549 bool OhosComponentsImpl::GetSubsystemName(const Value &component_name, std::string &subsystem_name, Err *err) const
550 {
551 const OhosComponent *component = GetComponentByName(component_name.string_value());
552 if (component == nullptr) {
553 *err = Err(component_name, "OHOS component : (" + component_name.string_value() + ") not found.");
554 return false;
555 }
556
557 subsystem_name = component->subsystem();
558 return true;
559 }
560
561 /**
562 * Ohos Components Public API
563 */
564
565 OhosComponents::OhosComponents() = default;
566
LoadOhosComponents(const std::string & build_dir,const Value * enable,const Value * indep,const Value * product,bool special_parts_switch,Err * err)567 bool OhosComponents::LoadOhosComponents(const std::string &build_dir,
568 const Value *enable, const Value *indep, const Value *product, bool special_parts_switch, Err *err)
569 {
570 if (!enable) {
571 // Not enabled
572 return true;
573 }
574 if (!enable->VerifyTypeIs(Value::BOOLEAN, err)) {
575 return false;
576 }
577
578 // Disabled
579 if (!enable->boolean_value()) {
580 return true;
581 }
582
583 mgr = new OhosComponentsImpl();
584
585 if (!mgr->LoadOhosComponents(build_dir, enable, indep, product, special_parts_switch, err)) {
586 delete mgr;
587 mgr = nullptr;
588 return false;
589 }
590
591 return true;
592 }
593
isOhosComponentsLoaded() const594 bool OhosComponents::isOhosComponentsLoaded() const
595 {
596 if (mgr == nullptr) {
597 return false;
598 } else {
599 return true;
600 }
601 }
602
GetExternalDepsLabel(const Value & external_dep,std::string & label,const Label & current_toolchain,int & whole_status,Err * err) const603 bool OhosComponents::GetExternalDepsLabel(const Value &external_dep, std::string &label,
604 const Label& current_toolchain, int &whole_status, Err *err) const
605 {
606 if (!mgr) {
607 if (err) {
608 *err = Err(external_dep, "You are compiling OpenHarmony components, but \n"
609 "\"ohos_components_support\" is not enabled or build_configs files are invalid.");
610 }
611 return false;
612 }
613 return mgr->GetExternalDepsLabel(external_dep, label, current_toolchain, whole_status, err);
614 }
615
GetPrivateDepsLabel(const Value & dep,std::string & label,const Label & current_toolchain,int & whole_status,Err * err) const616 bool OhosComponents::GetPrivateDepsLabel(const Value &dep, std::string &label,
617 const Label& current_toolchain, int &whole_status, Err *err) const
618 {
619 if (!mgr) {
620 if (err) {
621 *err = Err(dep, "You are compiling OpenHarmony components, but \n"
622 "\"ohos_components_support\" is not enabled or build_configs files are invalid.");
623 }
624 return false;
625 }
626 return mgr->GetPrivateDepsLabel(dep, label, current_toolchain, whole_status, err);
627 }
628
GetSubsystemName(const Value & part_name,std::string & label,Err * err) const629 bool OhosComponents::GetSubsystemName(const Value &part_name, std::string &label, Err *err) const
630 {
631 if (!mgr) {
632 if (err) {
633 *err = Err(part_name, "You are compiling OpenHarmony components, but \n"
634 "\"ohos_components_support\" is not enabled or build_configs files are invalid.");
635 }
636 return false;
637 }
638 return mgr->GetSubsystemName(part_name, label, err);
639 }
640
GetComponentByLabel(const std::string & label) const641 const OhosComponent *OhosComponents::GetComponentByLabel(const std::string &label) const
642 {
643 if (!mgr) {
644 return nullptr;
645 }
646 return mgr->matchComponentByLabel(label.c_str());
647 }
648
LoadOhosComponentsChecker(const std::string & build_dir,const Value * support,int checkType,unsigned int ruleSwitch)649 void OhosComponents::LoadOhosComponentsChecker(const std::string &build_dir, const Value *support, int checkType,
650 unsigned int ruleSwitch)
651 {
652 if (!support) {
653 return;
654 }
655 if (!support->boolean_value()) {
656 return;
657 }
658 if (checkType > OhosComponentChecker::CheckType::INTERCEPT_ALL ||
659 checkType <= OhosComponentChecker::CheckType::NONE) {
660 InnerApiPublicInfoGenerator::Init(build_dir, 0);
661 return;
662 }
663 OhosComponentChecker::Init(build_dir, checkType, ruleSwitch);
664 InnerApiPublicInfoGenerator::Init(build_dir, checkType);
665 return;
666 }
667
LoadOhosComponentsMapping(const std::string & build_dir,const Value * support,const Value * independent)668 void OhosComponents::LoadOhosComponentsMapping(const std::string& build_dir,
669 const Value *support, const Value *independent)
670 {
671 if (!support) {
672 return;
673 }
674 if (!support->boolean_value()) {
675 return;
676 }
677
678 if (!independent || !independent->boolean_value()) {
679 return;
680 }
681
682 OhosComponentMapping::Init(build_dir);
683 return;
684 }
685
GetComponentByName(const std::string & component_name)686 const OhosComponent *OhosComponents::GetComponentByName(const std::string &component_name) {
687 if (!mgr) {
688 return nullptr;
689 }
690 return mgr->GetComponentByName(component_name);
691 }
692
isOhosIndepCompilerEnable()693 bool OhosComponents::isOhosIndepCompilerEnable() {
694 return mgr && mgr->isOhosIndepCompilerEnable();
695 }
696