Files
CMake/Source/cmScanDepFormat.cxx
Brad King 3a71534402 Ninja: Restore support for Fortran in a symlinked build tree
Since commit f3eed2c49d (cmGlobalNinjaGenerator: use P1689 dependency
file format for Fortran, 2019-03-12, v3.20.0-rc1~454^2), Fortran stopped
working in a build tree whose path contains a symlink.  The reason is
that the P1689r3 format's `work-directory` field gets populated with the
realpath (via `getcwd`) of the build tree instead of the logical path to
the build tree used for generating relative paths in `build.ninja`.
This causes the `Fortran.dd` file to get absolute (real)paths to `.o`
files, and Ninja does not match them with the relative `.o` file paths
in `build.ninja`.

Fix this by dropping use of the `work-directory` field.  This restores
our prior approach of generating paths in the dyndep file using the same
forms of paths received from the buildsystem generator.  The P1689r3
paper's format may need to be revised to account for this.

Fixes: #21683
2021-05-12 10:15:43 -04:00

265 lines
8.8 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmScanDepFormat.h"
#include <cctype>
#include <cstdio>
#include <cm3p/json/reader.h>
#include <cm3p/json/value.h>
#include <cm3p/json/writer.h>
#include "cmsys/FStream.hxx"
#include "cmGeneratedFileStream.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
static bool ParseFilename(Json::Value const& val, std::string& result)
{
if (val.isString()) {
result = val.asString();
} else {
return false;
}
return true;
}
static Json::Value EncodeFilename(std::string const& path)
{
std::string data;
data.reserve(path.size());
for (auto const& byte : path) {
if (std::iscntrl(byte)) {
// Control characters.
data.append("\\u");
char buf[5];
std::snprintf(buf, sizeof(buf), "%04x", byte);
data.append(buf);
} else if (byte == '"' || byte == '\\') {
// Special JSON characters.
data.push_back('\\');
data.push_back(byte);
} else {
// Other data.
data.push_back(byte);
}
}
return data;
}
#define PARSE_BLOB(val, res) \
do { \
if (!ParseFilename(val, res)) { \
cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ", \
arg_pp, ": invalid blob")); \
return false; \
} \
} while (0)
#define PARSE_FILENAME(val, res) \
do { \
if (!ParseFilename(val, res)) { \
cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ", \
arg_pp, ": invalid filename")); \
return false; \
} \
\
if (!work_directory.empty() && !cmSystemTools::FileIsFullPath(res)) { \
res = cmStrCat(work_directory, '/', res); \
} \
} while (0)
bool cmScanDepFormat_P1689_Parse(std::string const& arg_pp, cmSourceInfo* info)
{
Json::Value ppio;
Json::Value const& ppi = ppio;
cmsys::ifstream ppf(arg_pp.c_str(), std::ios::in | std::ios::binary);
{
Json::Reader reader;
if (!reader.parse(ppf, ppio, false)) {
cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
arg_pp,
reader.getFormattedErrorMessages()));
return false;
}
}
Json::Value const& version = ppi["version"];
if (version.asUInt() != 0) {
cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
arg_pp, ": version ", version.asString()));
return false;
}
Json::Value const& rules = ppi["rules"];
if (rules.isArray()) {
if (rules.size() != 1) {
cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
arg_pp, ": expected 1 source entry"));
return false;
}
for (auto const& rule : rules) {
std::string work_directory;
Json::Value const& workdir = rule["work-directory"];
if (workdir.isString()) {
PARSE_BLOB(workdir, work_directory);
} else if (!workdir.isNull()) {
cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
arg_pp,
": work-directory is not a string"));
return false;
}
Json::Value const& depends = rule["depends"];
if (depends.isArray()) {
std::string depend_filename;
for (auto const& depend : depends) {
PARSE_FILENAME(depend, depend_filename);
info->Includes.push_back(depend_filename);
}
}
if (rule.isMember("future-compile")) {
Json::Value const& future_compile = rule["future-compile"];
if (future_compile.isMember("outputs")) {
Json::Value const& outputs = future_compile["outputs"];
if (outputs.isArray()) {
if (outputs.empty()) {
cmSystemTools::Error(
cmStrCat("-E cmake_ninja_dyndep failed to parse ", arg_pp,
": expected at least one 1 output"));
return false;
}
PARSE_FILENAME(outputs[0], info->PrimaryOutput);
}
}
if (future_compile.isMember("provides")) {
Json::Value const& provides = future_compile["provides"];
if (provides.isArray()) {
for (auto const& provide : provides) {
cmSourceReqInfo provide_info;
Json::Value const& logical_name = provide["logical-name"];
PARSE_BLOB(logical_name, provide_info.LogicalName);
if (provide.isMember("compiled-module-path")) {
Json::Value const& compiled_module_path =
provide["compiled-module-path"];
PARSE_FILENAME(compiled_module_path,
provide_info.CompiledModulePath);
} else {
provide_info.CompiledModulePath =
cmStrCat(provide_info.LogicalName, ".mod");
}
info->Provides.push_back(provide_info);
}
}
}
if (future_compile.isMember("requires")) {
Json::Value const& reqs = future_compile["requires"];
if (reqs.isArray()) {
for (auto const& require : reqs) {
cmSourceReqInfo require_info;
Json::Value const& logical_name = require["logical-name"];
PARSE_BLOB(logical_name, require_info.LogicalName);
if (require.isMember("compiled-module-path")) {
Json::Value const& compiled_module_path =
require["compiled-module-path"];
PARSE_FILENAME(compiled_module_path,
require_info.CompiledModulePath);
}
info->Requires.push_back(require_info);
}
}
}
}
}
}
return true;
}
bool cmScanDepFormat_P1689_Write(std::string const& path,
std::string const& input,
cmSourceInfo const& info)
{
Json::Value ddi(Json::objectValue);
ddi["version"] = 0;
ddi["revision"] = 0;
Json::Value& rules = ddi["rules"] = Json::arrayValue;
Json::Value rule(Json::objectValue);
Json::Value& inputs = rule["inputs"] = Json::arrayValue;
inputs.append(EncodeFilename(input));
Json::Value& rule_outputs = rule["outputs"] = Json::arrayValue;
rule_outputs.append(EncodeFilename(path));
Json::Value& depends = rule["depends"] = Json::arrayValue;
for (auto const& include : info.Includes) {
depends.append(EncodeFilename(include));
}
Json::Value& future_compile = rule["future-compile"] = Json::objectValue;
Json::Value& outputs = future_compile["outputs"] = Json::arrayValue;
outputs.append(info.PrimaryOutput);
Json::Value& provides = future_compile["provides"] = Json::arrayValue;
for (auto const& provide : info.Provides) {
Json::Value provide_obj(Json::objectValue);
auto const encoded = EncodeFilename(provide.LogicalName);
provide_obj["logical-name"] = encoded;
if (provide.CompiledModulePath.empty()) {
provide_obj["compiled-module-path"] = encoded;
} else {
provide_obj["compiled-module-path"] =
EncodeFilename(provide.CompiledModulePath);
}
// TODO: Source file tracking. See below.
provides.append(provide_obj);
}
Json::Value& reqs = future_compile["requires"] = Json::arrayValue;
for (auto const& require : info.Requires) {
Json::Value require_obj(Json::objectValue);
auto const encoded = EncodeFilename(require.LogicalName);
require_obj["logical-name"] = encoded;
if (require.CompiledModulePath.empty()) {
require_obj["compiled-module-path"] = encoded;
} else {
require_obj["compiled-module-path"] =
EncodeFilename(require.CompiledModulePath);
}
// TODO: Source filename inclusion. Requires collating with the provides
// filenames (as a sanity check if available on both sides).
reqs.append(require_obj);
}
rules.append(rule);
cmGeneratedFileStream ddif(path);
ddif << ddi;
return !!ddif;
}