1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
4 #include "cmScanDepFormat.h"
10 #include <cm/optional>
11 #include <cm/string_view>
12 #include <cmext/string_view>
14 #include <cm3p/json/reader.h>
15 #include <cm3p/json/value.h>
16 #include <cm3p/json/writer.h>
18 #include "cmsys/FStream.hxx"
20 #include "cmGeneratedFileStream.h"
21 #include "cmStringAlgorithms.h"
22 #include "cmSystemTools.h"
24 static bool ParseFilename(Json::Value
const& val
, std::string
& result
)
27 result
= val
.asString();
35 static Json::Value
EncodeFilename(std::string
const& path
)
38 data
.reserve(path
.size());
40 for (auto const& byte
: path
) {
41 if (std::iscntrl(byte
)) {
42 // Control characters.
45 std::snprintf(buf
, sizeof(buf
), "%04x", byte
);
47 } else if (byte
== '"' || byte
== '\\') {
48 // Special JSON characters.
60 #define PARSE_BLOB(val, res) \
62 if (!ParseFilename(val, res)) { \
63 cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ", \
64 arg_pp, ": invalid blob")); \
69 #define PARSE_FILENAME(val, res) \
71 if (!ParseFilename(val, res)) { \
72 cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ", \
73 arg_pp, ": invalid filename")); \
77 if (work_directory && !work_directory->empty() && \
78 !cmSystemTools::FileIsFullPath(res)) { \
79 res = cmStrCat(*work_directory, '/', res); \
83 bool cmScanDepFormat_P1689_Parse(std::string
const& arg_pp
,
87 Json::Value
const& ppi
= ppio
;
88 cmsys::ifstream
ppf(arg_pp
.c_str(), std::ios::in
| std::ios::binary
);
91 if (!reader
.parse(ppf
, ppio
, false)) {
92 cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
94 reader
.getFormattedErrorMessages()));
99 Json::Value
const& version
= ppi
["version"];
100 if (version
.asUInt() > 1) {
101 cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
102 arg_pp
, ": version ", version
.asString()));
106 Json::Value
const& rules
= ppi
["rules"];
107 if (rules
.isArray()) {
108 if (rules
.size() != 1) {
109 cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
110 arg_pp
, ": expected 1 source entry"));
114 for (auto const& rule
: rules
) {
115 cm::optional
<std::string
> work_directory
;
116 Json::Value
const& workdir
= rule
["work-directory"];
117 if (workdir
.isString()) {
119 PARSE_BLOB(workdir
, wd
);
120 work_directory
= std::move(wd
);
121 } else if (!workdir
.isNull()) {
122 cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
124 ": work-directory is not a string"));
128 if (rule
.isMember("primary-output")) {
129 Json::Value
const& primary_output
= rule
["primary-output"];
130 PARSE_FILENAME(primary_output
, info
->PrimaryOutput
);
133 if (rule
.isMember("outputs")) {
134 Json::Value
const& outputs
= rule
["outputs"];
135 if (outputs
.isArray()) {
136 for (auto const& output
: outputs
) {
137 std::string extra_output
;
138 PARSE_FILENAME(output
, extra_output
);
140 info
->ExtraOutputs
.emplace_back(extra_output
);
145 if (rule
.isMember("provides")) {
146 Json::Value
const& provides
= rule
["provides"];
147 if (!provides
.isArray()) {
148 cmSystemTools::Error(
149 cmStrCat("-E cmake_ninja_dyndep failed to parse ", arg_pp
,
150 ": provides is not an array"));
154 for (auto const& provide
: provides
) {
155 cmSourceReqInfo provide_info
;
157 Json::Value
const& logical_name
= provide
["logical-name"];
158 PARSE_BLOB(logical_name
, provide_info
.LogicalName
);
160 if (provide
.isMember("compiled-module-path")) {
161 Json::Value
const& compiled_module_path
=
162 provide
["compiled-module-path"];
163 PARSE_FILENAME(compiled_module_path
,
164 provide_info
.CompiledModulePath
);
167 if (provide
.isMember("unique-on-source-path")) {
168 Json::Value
const& unique_on_source_path
=
169 provide
["unique-on-source-path"];
170 if (!unique_on_source_path
.isBool()) {
171 cmSystemTools::Error(
172 cmStrCat("-E cmake_ninja_dyndep failed to parse ", arg_pp
,
173 ": unique-on-source-path is not a boolean"));
176 provide_info
.UseSourcePath
= unique_on_source_path
.asBool();
178 provide_info
.UseSourcePath
= false;
181 if (provide
.isMember("source-path")) {
182 Json::Value
const& source_path
= provide
["source-path"];
183 PARSE_FILENAME(source_path
, provide_info
.SourcePath
);
184 } else if (provide_info
.UseSourcePath
) {
185 cmSystemTools::Error(
186 cmStrCat("-E cmake_ninja_dyndep failed to parse ", arg_pp
,
187 ": source-path is missing"));
191 if (provide
.isMember("is-interface")) {
192 Json::Value
const& is_interface
= provide
["is-interface"];
193 if (!is_interface
.isBool()) {
194 cmSystemTools::Error(
195 cmStrCat("-E cmake_ninja_dyndep failed to parse ", arg_pp
,
196 ": is-interface is not a boolean"));
199 provide_info
.IsInterface
= is_interface
.asBool();
201 provide_info
.IsInterface
= true;
204 info
->Provides
.push_back(provide_info
);
208 if (rule
.isMember("requires")) {
209 Json::Value
const& reqs
= rule
["requires"];
210 if (!reqs
.isArray()) {
211 cmSystemTools::Error(
212 cmStrCat("-E cmake_ninja_dyndep failed to parse ", arg_pp
,
213 ": requires is not an array"));
217 for (auto const& require
: reqs
) {
218 cmSourceReqInfo require_info
;
220 Json::Value
const& logical_name
= require
["logical-name"];
221 PARSE_BLOB(logical_name
, require_info
.LogicalName
);
223 if (require
.isMember("compiled-module-path")) {
224 Json::Value
const& compiled_module_path
=
225 require
["compiled-module-path"];
226 PARSE_FILENAME(compiled_module_path
,
227 require_info
.CompiledModulePath
);
230 if (require
.isMember("unique-on-source-path")) {
231 Json::Value
const& unique_on_source_path
=
232 require
["unique-on-source-path"];
233 if (!unique_on_source_path
.isBool()) {
234 cmSystemTools::Error(
235 cmStrCat("-E cmake_ninja_dyndep failed to parse ", arg_pp
,
236 ": unique-on-source-path is not a boolean"));
239 require_info
.UseSourcePath
= unique_on_source_path
.asBool();
241 require_info
.UseSourcePath
= false;
244 if (require
.isMember("source-path")) {
245 Json::Value
const& source_path
= require
["source-path"];
246 PARSE_FILENAME(source_path
, require_info
.SourcePath
);
247 } else if (require_info
.UseSourcePath
) {
248 cmSystemTools::Error(
249 cmStrCat("-E cmake_ninja_dyndep failed to parse ", arg_pp
,
250 ": source-path is missing"));
254 if (require
.isMember("lookup-method")) {
255 Json::Value
const& lookup_method
= require
["lookup-method"];
256 if (!lookup_method
.isString()) {
257 cmSystemTools::Error(
258 cmStrCat("-E cmake_ninja_dyndep failed to parse ", arg_pp
,
259 ": lookup-method is not a string"));
263 std::string lookup_method_str
= lookup_method
.asString();
264 if (lookup_method_str
== "by-name"_s
) {
265 require_info
.Method
= LookupMethod::ByName
;
266 } else if (lookup_method_str
== "include-angle"_s
) {
267 require_info
.Method
= LookupMethod::IncludeAngle
;
268 } else if (lookup_method_str
== "include-quote"_s
) {
269 require_info
.Method
= LookupMethod::IncludeQuote
;
271 cmSystemTools::Error(cmStrCat(
272 "-E cmake_ninja_dyndep failed to parse ", arg_pp
,
273 ": lookup-method is not a valid: ", lookup_method_str
));
276 } else if (require_info
.UseSourcePath
) {
277 require_info
.Method
= LookupMethod::ByName
;
280 info
->Requires
.push_back(require_info
);
289 bool cmScanDepFormat_P1689_Write(std::string
const& path
,
290 cmScanDepInfo
const& info
)
292 Json::Value
ddi(Json::objectValue
);
296 Json::Value
& rules
= ddi
["rules"] = Json::arrayValue
;
298 Json::Value
rule(Json::objectValue
);
300 rule
["primary-output"] = EncodeFilename(info
.PrimaryOutput
);
302 Json::Value
& rule_outputs
= rule
["outputs"] = Json::arrayValue
;
303 for (auto const& output
: info
.ExtraOutputs
) {
304 rule_outputs
.append(EncodeFilename(output
));
307 Json::Value
& provides
= rule
["provides"] = Json::arrayValue
;
308 for (auto const& provide
: info
.Provides
) {
309 Json::Value
provide_obj(Json::objectValue
);
310 auto const encoded
= EncodeFilename(provide
.LogicalName
);
311 provide_obj
["logical-name"] = encoded
;
312 if (!provide
.CompiledModulePath
.empty()) {
313 provide_obj
["compiled-module-path"] =
314 EncodeFilename(provide
.CompiledModulePath
);
317 if (provide
.UseSourcePath
) {
318 provide_obj
["unique-on-source-path"] = true;
319 provide_obj
["source-path"] = EncodeFilename(provide
.SourcePath
);
320 } else if (!provide
.SourcePath
.empty()) {
321 provide_obj
["source-path"] = EncodeFilename(provide
.SourcePath
);
324 provide_obj
["is-interface"] = provide
.IsInterface
;
326 provides
.append(provide_obj
);
329 Json::Value
& reqs
= rule
["requires"] = Json::arrayValue
;
330 for (auto const& require
: info
.Requires
) {
331 Json::Value
require_obj(Json::objectValue
);
332 auto const encoded
= EncodeFilename(require
.LogicalName
);
333 require_obj
["logical-name"] = encoded
;
334 if (!require
.CompiledModulePath
.empty()) {
335 require_obj
["compiled-module-path"] =
336 EncodeFilename(require
.CompiledModulePath
);
339 if (require
.UseSourcePath
) {
340 require_obj
["unique-on-source-path"] = true;
341 require_obj
["source-path"] = EncodeFilename(require
.SourcePath
);
342 } else if (!require
.SourcePath
.empty()) {
343 require_obj
["source-path"] = EncodeFilename(require
.SourcePath
);
346 const char* lookup_method
= nullptr;
347 switch (require
.Method
) {
348 case LookupMethod::ByName
:
349 // No explicit value needed for the default.
351 case LookupMethod::IncludeAngle
:
352 lookup_method
= "include-angle";
354 case LookupMethod::IncludeQuote
:
355 lookup_method
= "include-quote";
359 require_obj
["lookup-method"] = lookup_method
;
362 reqs
.append(require_obj
);
367 cmGeneratedFileStream
ddif(path
);