xref: /aosp_15_r20/external/tensorflow/tensorflow/core/api_def/update_api_def.cc (revision b6fb3261f9314811a0f4371741dbb8839866f948)
1 /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6 
7 http://www.apache.org/licenses/LICENSE-2.0
8 
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15 #include "tensorflow/core/api_def/update_api_def.h"
16 
17 #include <ctype.h>
18 
19 #include <algorithm>
20 #include <string>
21 #include <vector>
22 
23 #include "tensorflow/core/api_def/excluded_ops.h"
24 #include "tensorflow/core/framework/api_def.pb.h"
25 #include "tensorflow/core/framework/op.h"
26 #include "tensorflow/core/framework/op_def_builder.h"
27 #include "tensorflow/core/framework/op_gen_lib.h"
28 #include "tensorflow/core/lib/core/status.h"
29 #include "tensorflow/core/lib/io/path.h"
30 #include "tensorflow/core/lib/strings/stringprintf.h"
31 #include "tensorflow/core/platform/env.h"
32 #include "tensorflow/core/platform/protobuf.h"
33 
34 namespace tensorflow {
35 
36 namespace {
37 constexpr char kApiDefFileFormat[] = "api_def_%s.pbtxt";
38 // TODO(annarev): look into supporting other prefixes, not just 'doc'.
39 constexpr char kDocStart[] = ".Doc(R\"doc(";
40 constexpr char kDocEnd[] = ")doc\")";
41 
42 // Updates api_def based on the given op.
FillBaseApiDef(ApiDef * api_def,const OpDef & op)43 void FillBaseApiDef(ApiDef* api_def, const OpDef& op) {
44   api_def->set_graph_op_name(op.name());
45   // Add arg docs
46   for (auto& input_arg : op.input_arg()) {
47     if (!input_arg.description().empty()) {
48       auto* api_def_in_arg = api_def->add_in_arg();
49       api_def_in_arg->set_name(input_arg.name());
50       api_def_in_arg->set_description(input_arg.description());
51     }
52   }
53   for (auto& output_arg : op.output_arg()) {
54     if (!output_arg.description().empty()) {
55       auto* api_def_out_arg = api_def->add_out_arg();
56       api_def_out_arg->set_name(output_arg.name());
57       api_def_out_arg->set_description(output_arg.description());
58     }
59   }
60   // Add attr docs
61   for (auto& attr : op.attr()) {
62     if (!attr.description().empty()) {
63       auto* api_def_attr = api_def->add_attr();
64       api_def_attr->set_name(attr.name());
65       api_def_attr->set_description(attr.description());
66     }
67   }
68   // Add docs
69   api_def->set_summary(op.summary());
70   api_def->set_description(op.description());
71 }
72 
73 // Returns true if op has any description or summary.
OpHasDocs(const OpDef & op)74 bool OpHasDocs(const OpDef& op) {
75   if (!op.summary().empty() || !op.description().empty()) {
76     return true;
77   }
78   for (const auto& arg : op.input_arg()) {
79     if (!arg.description().empty()) {
80       return true;
81     }
82   }
83   for (const auto& arg : op.output_arg()) {
84     if (!arg.description().empty()) {
85       return true;
86     }
87   }
88   for (const auto& attr : op.attr()) {
89     if (!attr.description().empty()) {
90       return true;
91     }
92   }
93   return false;
94 }
95 
96 // Returns true if summary and all descriptions are the same in op1
97 // and op2.
CheckDocsMatch(const OpDef & op1,const OpDef & op2)98 bool CheckDocsMatch(const OpDef& op1, const OpDef& op2) {
99   if (op1.summary() != op2.summary() ||
100       op1.description() != op2.description() ||
101       op1.input_arg_size() != op2.input_arg_size() ||
102       op1.output_arg_size() != op2.output_arg_size() ||
103       op1.attr_size() != op2.attr_size()) {
104     return false;
105   }
106   // Iterate over args and attrs to compare their docs.
107   for (int i = 0; i < op1.input_arg_size(); ++i) {
108     if (op1.input_arg(i).description() != op2.input_arg(i).description()) {
109       return false;
110     }
111   }
112   for (int i = 0; i < op1.output_arg_size(); ++i) {
113     if (op1.output_arg(i).description() != op2.output_arg(i).description()) {
114       return false;
115     }
116   }
117   for (int i = 0; i < op1.attr_size(); ++i) {
118     if (op1.attr(i).description() != op2.attr(i).description()) {
119       return false;
120     }
121   }
122   return true;
123 }
124 
125 // Returns true if descriptions and summaries in op match a
126 // given single doc-string.
ValidateOpDocs(const OpDef & op,const string & doc)127 bool ValidateOpDocs(const OpDef& op, const string& doc) {
128   OpDefBuilder b(op.name());
129   // We don't really care about type we use for arguments and
130   // attributes. We just want to make sure attribute and argument names
131   // are added so that descriptions can be assigned to them when parsing
132   // documentation.
133   for (const auto& arg : op.input_arg()) {
134     b.Input(arg.name() + ":string");
135   }
136   for (const auto& arg : op.output_arg()) {
137     b.Output(arg.name() + ":string");
138   }
139   for (const auto& attr : op.attr()) {
140     b.Attr(attr.name() + ":string");
141   }
142   b.Doc(doc);
143   OpRegistrationData op_reg_data;
144   TF_CHECK_OK(b.Finalize(&op_reg_data));
145   return CheckDocsMatch(op, op_reg_data.op_def);
146 }
147 }  // namespace
148 
RemoveDoc(const OpDef & op,const string & file_contents,size_t start_location)149 string RemoveDoc(const OpDef& op, const string& file_contents,
150                  size_t start_location) {
151   // Look for a line starting with .Doc( after the REGISTER_OP.
152   const auto doc_start_location = file_contents.find(kDocStart, start_location);
153   const string format_error = strings::Printf(
154       "Could not find %s doc for removal. Make sure the doc is defined with "
155       "'%s' prefix and '%s' suffix or remove the doc manually.",
156       op.name().c_str(), kDocStart, kDocEnd);
157   if (doc_start_location == string::npos) {
158     std::cerr << format_error << std::endl;
159     LOG(ERROR) << "Didn't find doc start";
160     return file_contents;
161   }
162   const auto doc_end_location = file_contents.find(kDocEnd, doc_start_location);
163   if (doc_end_location == string::npos) {
164     LOG(ERROR) << "Didn't find doc start";
165     std::cerr << format_error << std::endl;
166     return file_contents;
167   }
168 
169   const auto doc_start_size = sizeof(kDocStart) - 1;
170   string doc_text = file_contents.substr(
171       doc_start_location + doc_start_size,
172       doc_end_location - doc_start_location - doc_start_size);
173 
174   // Make sure the doc text we found actually matches OpDef docs to
175   // avoid removing incorrect text.
176   if (!ValidateOpDocs(op, doc_text)) {
177     LOG(ERROR) << "Invalid doc: " << doc_text;
178     std::cerr << format_error << std::endl;
179     return file_contents;
180   }
181   // Remove .Doc call.
182   auto before_doc = file_contents.substr(0, doc_start_location);
183   absl::StripTrailingAsciiWhitespace(&before_doc);
184   return before_doc +
185          file_contents.substr(doc_end_location + sizeof(kDocEnd) - 1);
186 }
187 
188 namespace {
189 // Remove .Doc calls that follow REGISTER_OP calls for the given ops.
190 // We search for REGISTER_OP calls in the given op_files list.
RemoveDocs(const std::vector<const OpDef * > & ops,const std::vector<string> & op_files)191 void RemoveDocs(const std::vector<const OpDef*>& ops,
192                 const std::vector<string>& op_files) {
193   // Set of ops that we already found REGISTER_OP calls for.
194   std::set<string> processed_ops;
195 
196   for (const auto& file : op_files) {
197     string file_contents;
198     bool file_contents_updated = false;
199     TF_CHECK_OK(ReadFileToString(Env::Default(), file, &file_contents));
200 
201     for (auto op : ops) {
202       if (processed_ops.find(op->name()) != processed_ops.end()) {
203         // We already found REGISTER_OP call for this op in another file.
204         continue;
205       }
206       string register_call =
207           strings::Printf("REGISTER_OP(\"%s\")", op->name().c_str());
208       const auto register_call_location = file_contents.find(register_call);
209       // Find REGISTER_OP(OpName) call.
210       if (register_call_location == string::npos) {
211         continue;
212       }
213       std::cout << "Removing .Doc call for " << op->name() << " from " << file
214                 << "." << std::endl;
215       file_contents = RemoveDoc(*op, file_contents, register_call_location);
216       file_contents_updated = true;
217 
218       processed_ops.insert(op->name());
219     }
220     if (file_contents_updated) {
221       TF_CHECK_OK(WriteStringToFile(Env::Default(), file, file_contents))
222           << "Could not remove .Doc calls in " << file
223           << ". Make sure the file is writable.";
224     }
225   }
226 }
227 }  // namespace
228 
229 // Returns ApiDefs text representation in multi-line format
230 // constructed based on the given op.
CreateApiDef(const OpDef & op)231 string CreateApiDef(const OpDef& op) {
232   ApiDefs api_defs;
233   FillBaseApiDef(api_defs.add_op(), op);
234 
235   const std::vector<string> multi_line_fields = {"description"};
236   std::string new_api_defs_str;
237   ::tensorflow::protobuf::TextFormat::PrintToString(api_defs,
238                                                     &new_api_defs_str);
239   return PBTxtToMultiline(new_api_defs_str, multi_line_fields);
240 }
241 
242 // Creates ApiDef files for any new ops.
243 // If op_file_pattern is not empty, then also removes .Doc calls from
244 // new op registrations in these files.
CreateApiDefs(const OpList & ops,const string & api_def_dir,const string & op_file_pattern)245 void CreateApiDefs(const OpList& ops, const string& api_def_dir,
246                    const string& op_file_pattern) {
247   auto* excluded_ops = GetExcludedOps();
248   std::vector<const OpDef*> new_ops_with_docs;
249 
250   for (const auto& op : ops.op()) {
251     if (excluded_ops->find(op.name()) != excluded_ops->end()) {
252       continue;
253     }
254     // Form the expected ApiDef path.
255     string file_path =
256         io::JoinPath(tensorflow::string(api_def_dir), kApiDefFileFormat);
257     file_path = strings::Printf(file_path.c_str(), op.name().c_str());
258 
259     // Create ApiDef if it doesn't exist.
260     if (!Env::Default()->FileExists(file_path).ok()) {
261       std::cout << "Creating ApiDef file " << file_path << std::endl;
262       const auto& api_def_text = CreateApiDef(op);
263       TF_CHECK_OK(WriteStringToFile(Env::Default(), file_path, api_def_text));
264 
265       if (OpHasDocs(op)) {
266         new_ops_with_docs.push_back(&op);
267       }
268     }
269   }
270   if (!op_file_pattern.empty()) {
271     std::vector<string> op_files;
272     TF_CHECK_OK(Env::Default()->GetMatchingPaths(op_file_pattern, &op_files));
273     RemoveDocs(new_ops_with_docs, op_files);
274   }
275 }
276 }  // namespace tensorflow
277