Commit e2a9467b authored by Kenton Varda's avatar Kenton Varda

Change capnp tool to use filesystem API.

In addition to cleaner code, this has the effect of fixing a number of issues:
- Fixes #494: Windows paths are now parsed and handled a lot better. On Windows, backslashes are handled appropriately, as are absolute paths with drive letters.
- Fixes #288: Specifing a source path starting with ".." without an appropriate --src-prefix will no longer cause output files to be generated outside of the specified output directory. Instead, the output name will be based on the absolute path, and a warning will be written since this is probably not what anyone wants.
- It's no longer necessary for --src-prefix to exactly match character-for-character. Instead, the prefix and source paths are canonicalized before matching. It even works if one is relative and the other absolute.
- Relative paths are now consistently always evaluated in path space, so ".." always cancels the previous path component and the OS/filesystem never even sees that it was there. This used to be the case in some places but not others. This behavior is important for symlink-heavy source trees: the path "foo/some-symlink/.." is now always designates "foo", rather than designating the parent directory of the symlink's target.

This change does not update the code generator plugins nor the SchemaParser API. Those will come in separate commits.
parent 6d4107f4
......@@ -65,7 +65,7 @@ static const char VERSION_STRING[] = "Cap'n Proto version " VERSION;
class CompilerMain final: public GlobalErrorReporter {
public:
explicit CompilerMain(kj::ProcessContext& context)
: context(context), loader(*this) {}
: context(context), disk(kj::newDiskFilesystem()), loader(*this) {}
kj::MainFunc getMain() {
if (context.getProgramName().endsWith("capnpc")) {
......@@ -305,8 +305,12 @@ public:
// shared options
kj::MainBuilder::Validity addImportPath(kj::StringPtr path) {
loader.addImportPath(kj::heapString(path));
KJ_IF_MAYBE(dir, getSourceDirectory(path, false)) {
loader.addImportPath(*dir);
return true;
} else {
return "no such directory";
}
}
kj::MainBuilder::Validity noStandardImport() {
......@@ -315,31 +319,32 @@ public:
}
kj::MainBuilder::Validity addSource(kj::StringPtr file) {
// Strip redundant "./" prefixes to make src-prefix matching more lenient.
while (file.startsWith("./")) {
file = file.slice(2);
// Remove redundant slashes as well (e.g. ".////foo" -> "foo").
while (file.startsWith("/")) {
file = file.slice(1);
}
}
if (!compilerConstructed) {
compiler = compilerSpace.construct(annotationFlag);
compilerConstructed = true;
}
if (addStandardImportPaths) {
loader.addImportPath(kj::heapString("/usr/local/include"));
loader.addImportPath(kj::heapString("/usr/include"));
static constexpr kj::StringPtr STANDARD_IMPORT_PATHS[] = {
"/usr/local/include"_kj,
"/usr/include"_kj,
#ifdef CAPNP_INCLUDE_DIR
loader.addImportPath(kj::heapString(CAPNP_INCLUDE_DIR));
KJ_CONCAT(CAPNP_INCLUDE_DIR, _kj),
#endif
};
for (auto path: STANDARD_IMPORT_PATHS) {
KJ_IF_MAYBE(dir, getSourceDirectory(path, false)) {
loader.addImportPath(*dir);
} else {
// ignore standard path that doesn't exist
}
}
addStandardImportPaths = false;
}
KJ_IF_MAYBE(module, loadModule(file)) {
auto dirPathPair = interpretSourceFile(file);
KJ_IF_MAYBE(module, loader.loadModule(dirPathPair.dir, dirPathPair.path)) {
uint64_t id = compiler->add(*module);
compiler->eagerlyCompile(id, compileEagerness);
sourceFiles.add(SourceFile { id, module->getSourceName(), &*module });
......@@ -350,20 +355,6 @@ public:
return true;
}
private:
kj::Maybe<Module&> loadModule(kj::StringPtr file) {
size_t longestPrefix = 0;
for (auto& prefix: sourcePrefixes) {
if (file.startsWith(prefix)) {
longestPrefix = kj::max(longestPrefix, prefix.size());
}
}
kj::StringPtr canonicalName = file.slice(longestPrefix);
return loader.loadModule(file, canonicalName);
}
public:
// =====================================================================================
// "id" command
......@@ -434,23 +425,12 @@ public:
}
kj::MainBuilder::Validity addSourcePrefix(kj::StringPtr prefix) {
// Strip redundant "./" prefixes to make src-prefix matching more lenient.
while (prefix.startsWith("./")) {
prefix = prefix.slice(2);
}
if (prefix == "" || prefix == ".") {
// Irrelevant prefix.
return true;
}
if (prefix.endsWith("/")) {
sourcePrefixes.add(kj::heapString(prefix));
if (getSourceDirectory(prefix, true) == nullptr) {
return "no such directory";
} else {
sourcePrefixes.add(kj::str(prefix, '/'));
}
return true;
}
}
kj::MainBuilder::Validity generateOutput() {
if (hadErrors()) {
......@@ -1725,8 +1705,11 @@ public:
public:
// =====================================================================================
void addError(kj::StringPtr file, SourcePos start, SourcePos end,
void addError(kj::ReadableDirectory& directory, kj::PathPtr path,
SourcePos start, SourcePos end,
kj::StringPtr message) override {
auto file = getDisplayName(directory, path);
kj::String wholeMessage;
if (end.line == start.line) {
if (end.column == start.column) {
......@@ -1751,6 +1734,7 @@ public:
private:
kj::ProcessContext& context;
kj::Own<kj::Filesystem> disk;
ModuleLoader loader;
kj::SpaceFor<Compiler> compilerSpace;
bool compilerConstructed = false;
......@@ -1764,7 +1748,24 @@ private:
// of those schemas, plus the parent nodes of any dependencies. This is what most code generators
// require to function.
kj::Vector<kj::String> sourcePrefixes;
struct SourceDirectory {
kj::Path path;
kj::Own<kj::ReadableDirectory> dir;
bool isSourcePrefix;
};
std::map<kj::PathPtr, SourceDirectory> sourceDirectories;
// For each import path and source prefix, tracks the directory object we opened for it.
//
// Use via getSourceDirectory().
std::map<kj::ReadableDirectory*, kj::String> dirPrefixes;
// For each open directory object, maps to a path prefix to add when displaying this path in
// error messages. This keeps track of the original directory name as given by the user, before
// canonicalization.
//
// Use via getDisplayName().
bool addStandardImportPaths = true;
Format convertFrom = Format::BINARY;
......@@ -1795,6 +1796,112 @@ private:
kj::Vector<OutputDirective> outputs;
bool hadErrors_ = false;
kj::Maybe<kj::ReadableDirectory&> getSourceDirectory(
kj::StringPtr pathStr, bool isSourcePrefix) {
auto cwd = disk->getCurrentPath();
auto path = cwd.evalNative(pathStr);
if (path.size() == 0) return disk->getRoot();
auto iter = sourceDirectories.find(path);
if (iter != sourceDirectories.end()) {
iter->second.isSourcePrefix = iter->second.isSourcePrefix || isSourcePrefix;
return *iter->second.dir;
}
if (path == cwd) {
// Slight hack if the working directory is explicitly specified:
// - We want to avoid opening a new copy of the working directory, as tryOpenSubdir() would
// do.
// - If isSourcePrefix is true, we need to add it to sourceDirectories to track that.
// Otherwise we don't need to add it at all.
// - We do not need to add it to dirPrefixes since the cwd is already handled in
// getDisplayName().
auto& result = disk->getCurrent();
if (isSourcePrefix) {
kj::PathPtr key = path;
kj::Own<kj::ReadableDirectory> fakeOwn(&result, kj::NullDisposer::instance);
KJ_ASSERT(sourceDirectories.insert(std::make_pair(key,
SourceDirectory { kj::mv(path), kj::mv(fakeOwn), isSourcePrefix })).second);
}
return result;
}
KJ_IF_MAYBE(dir, disk->getRoot().tryOpenSubdir(path)) {
auto& result = *dir->get();
kj::PathPtr key = path;
KJ_ASSERT(sourceDirectories.insert(std::make_pair(key,
SourceDirectory { kj::mv(path), kj::mv(*dir), isSourcePrefix })).second);
#if _WIN32
kj::String prefix = pathStr.endsWith("/") || pathStr.endsWith("\\")
? kj::str(pathStr) : kj::str(pathStr, '\\');
#else
kj::String prefix = pathStr.endsWith("/") ? kj::str(pathStr) : kj::str(pathStr, '/');
#endif
KJ_ASSERT(dirPrefixes.insert(std::make_pair(&result, kj::mv(prefix))).second);
return result;
} else {
return nullptr;
}
}
struct DirPathPair {
kj::ReadableDirectory& dir;
kj::Path path;
};
DirPathPair interpretSourceFile(kj::StringPtr pathStr) {
auto cwd = disk->getCurrentPath();
auto path = cwd.evalNative(pathStr);
KJ_REQUIRE(path.size() > 0);
for (size_t i = path.size() - 1; i > 0; i--) {
auto prefix = path.slice(0, i);
auto remainder = path.slice(i, path.size());
auto iter = sourceDirectories.find(prefix);
if (iter != sourceDirectories.end() && iter->second.isSourcePrefix) {
return { *iter->second.dir, remainder.clone() };
}
}
// No source prefix matched. Fall back to heuristic: try stripping the current directory,
// otherwise don't strip anything.
if (path.startsWith(cwd)) {
return { disk->getCurrent(), path.slice(cwd.size(), path.size()).clone() };
} else {
// Hmm, no src-prefix matched and the file isn't even in the current directory. This might
// be OK if we aren't generating any output anyway, but otherwise the results will almost
// certainly not be what the user wanted. Let's print a warning, unless the output directives
// are ones which we know do not produce output files. This is a hack.
for (auto& output: outputs) {
auto name = kj::str(output.name);
if (name != "-" && name != "capnp") {
context.warning(kj::str(pathStr,
": File is not in the current directory and does not match any prefix defined with "
"--src-prefix. Please pass an appropriate --src-prefix so I can figure out where to "
"write the output for this file."));
break;
}
}
return { disk->getRoot(), kj::mv(path) };
}
}
kj::String getDisplayName(kj::ReadableDirectory& dir, kj::PathPtr path) {
auto iter = dirPrefixes.find(&dir);
if (iter != dirPrefixes.end()) {
return kj::str(iter->second, path.toNativeString());
} else if (&dir == &disk->getRoot()) {
return path.toNativeString(true);
} else if (&dir == &disk->getCurrent()) {
return path.toNativeString(false);
} else {
KJ_FAIL_ASSERT("unrecognized directory");
}
}
};
} // namespace compiler
......
......@@ -30,6 +30,7 @@
#include <kj/string.h>
#include <kj/exception.h>
#include <kj/vector.h>
#include <kj/filesystem.h>
namespace capnp {
namespace compiler {
......@@ -68,7 +69,8 @@ public:
uint column;
};
virtual void addError(kj::StringPtr file, SourcePos start, SourcePos end,
virtual void addError(kj::ReadableDirectory& directory, kj::PathPtr path,
SourcePos start, SourcePos end,
kj::StringPtr message) = 0;
// Report an error at the given location in the given file.
......
......@@ -28,226 +28,50 @@
#include <kj/io.h>
#include <capnp/message.h>
#include <map>
#include <kj/miniposix.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#if _WIN32
#include <windows.h>
#else
#include <sys/mman.h>
#endif
namespace capnp {
namespace compiler {
namespace {
class MmapDisposer: public kj::ArrayDisposer {
protected:
void disposeImpl(void* firstElement, size_t elementSize, size_t elementCount,
size_t capacity, void (*destroyElement)(void*)) const {
#if _WIN32
KJ_ASSERT(UnmapViewOfFile(firstElement));
#else
munmap(firstElement, elementSize * elementCount);
#endif
}
};
KJ_CONSTEXPR(static const) MmapDisposer mmapDisposer = MmapDisposer();
kj::Array<const byte> mmapForRead(kj::StringPtr filename) {
int fd;
// We already established that the file exists, so this should not fail.
KJ_SYSCALL(fd = open(filename.cStr(), O_RDONLY), filename);
kj::AutoCloseFd closer(fd);
struct stat stats;
KJ_SYSCALL(fstat(fd, &stats));
if (S_ISREG(stats.st_mode)) {
if (stats.st_size == 0) {
// mmap()ing zero bytes will fail.
return nullptr;
}
// Regular file. Just mmap() it.
#if _WIN32
HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
KJ_ASSERT(handle != INVALID_HANDLE_VALUE);
HANDLE mappingHandle = CreateFileMapping(
handle, NULL, PAGE_READONLY, 0, stats.st_size, NULL);
KJ_ASSERT(mappingHandle != INVALID_HANDLE_VALUE);
KJ_DEFER(KJ_ASSERT(CloseHandle(mappingHandle)));
const void* mapping = MapViewOfFile(mappingHandle, FILE_MAP_READ, 0, 0, stats.st_size);
#else // _WIN32
const void* mapping = mmap(NULL, stats.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (mapping == MAP_FAILED) {
KJ_FAIL_SYSCALL("mmap", errno, filename);
}
#endif // _WIN32, else
return kj::Array<const byte>(
reinterpret_cast<const byte*>(mapping), stats.st_size, mmapDisposer);
} else {
// This could be a stream of some sort, like a pipe. Fall back to read().
// TODO(cleanup): This does a lot of copies. Not sure I care.
kj::Vector<byte> data(8192);
byte buffer[4096];
for (;;) {
kj::miniposix::ssize_t n;
KJ_SYSCALL(n = read(fd, buffer, sizeof(buffer)));
if (n == 0) break;
data.addAll(buffer, buffer + n);
}
return data.releaseAsArray();
}
}
static char* canonicalizePath(char* path) {
// Taken from some old C code of mine.
// Preconditions:
// - path has already been determined to be relative, perhaps because the pointer actually points
// into the middle of some larger path string, in which case it must point to the character
// immediately after a '/'.
// Invariants:
// - src points to the beginning of a path component.
// - dst points to the location where the path component should end up, if it is not special.
// - src == path or src[-1] == '/'.
// - dst == path or dst[-1] == '/'.
char* src = path;
char* dst = path;
char* locked = dst; // dst cannot backtrack past this
char* partEnd;
bool hasMore;
for (;;) {
while (*src == '/') {
// Skip duplicate slash.
++src;
}
partEnd = strchr(src, '/');
hasMore = partEnd != NULL;
if (hasMore) {
*partEnd = '\0';
} else {
partEnd = src + strlen(src);
}
if (strcmp(src, ".") == 0) {
// Skip it.
} else if (strcmp(src, "..") == 0) {
if (dst > locked) {
// Backtrack over last path component.
--dst;
while (dst > locked && dst[-1] != '/') --dst;
} else {
locked += 3;
goto copy;
}
} else {
// Copy if needed.
copy:
if (dst < src) {
memmove(dst, src, partEnd - src);
dst += partEnd - src;
} else {
dst = partEnd;
}
*dst++ = '/';
}
if (hasMore) {
src = partEnd + 1;
} else {
// Oops, we have to remove the trailing '/'.
if (dst == path) {
// Oops, there is no trailing '/'. We have to return ".".
strcpy(path, ".");
return path + 1;
} else {
// Remove the trailing '/'. Note that this means that opening the file will work even
// if it is not a directory, where normally it should fail on non-directories when a
// trailing '/' is present. If this is a problem, we need to add some sort of special
// handling for this case where we stat() it separately to check if it is a directory,
// because Ekam findInput will not accept a trailing '/'.
--dst;
*dst = '\0';
return dst;
}
}
}
}
kj::String canonicalizePath(kj::StringPtr path) {
KJ_STACK_ARRAY(char, result, path.size() + 1, 128, 512);
strcpy(result.begin(), path.begin());
char* start = path.startsWith("/") ? result.begin() + 1 : result.begin();
char* end = canonicalizePath(start);
return kj::heapString(result.slice(0, end - result.begin()));
}
kj::String catPath(kj::StringPtr base, kj::StringPtr add) {
if (add.size() > 0 && add[0] == '/') {
return kj::heapString(add);
}
const char* pos = base.end();
while (pos > base.begin() && pos[-1] != '/') {
--pos;
}
return kj::str(base.slice(0, pos - base.begin()), add);
}
} // namespace
class ModuleLoader::Impl {
public:
Impl(GlobalErrorReporter& errorReporter): errorReporter(errorReporter) {}
Impl(GlobalErrorReporter& errorReporter)
: errorReporter(errorReporter) {}
void addImportPath(kj::String path) {
searchPath.add(kj::heapString(kj::mv(path)));
void addImportPath(kj::ReadableDirectory& dir) {
searchPath.add(&dir);
}
kj::Maybe<Module&> loadModule(kj::StringPtr localName, kj::StringPtr sourceName);
kj::Maybe<Module&> loadModuleFromSearchPath(kj::StringPtr sourceName);
kj::Maybe<kj::Array<const byte>> readEmbed(kj::StringPtr localName, kj::StringPtr sourceName);
kj::Maybe<kj::Array<const byte>> readEmbedFromSearchPath(kj::StringPtr sourceName);
kj::Maybe<Module&> loadModule(kj::ReadableDirectory& dir, kj::PathPtr path);
kj::Maybe<Module&> loadModuleFromSearchPath(kj::PathPtr path);
kj::Maybe<kj::Array<const byte>> readEmbed(kj::ReadableDirectory& dir, kj::PathPtr path);
kj::Maybe<kj::Array<const byte>> readEmbedFromSearchPath(kj::PathPtr path);
GlobalErrorReporter& getErrorReporter() { return errorReporter; }
private:
GlobalErrorReporter& errorReporter;
kj::Vector<kj::String> searchPath;
std::map<kj::StringPtr, kj::Own<Module>> modules;
kj::Vector<kj::ReadableDirectory*> searchPath;
std::map<std::pair<kj::ReadableDirectory*, kj::PathPtr>, kj::Own<Module>> modules;
};
class ModuleLoader::ModuleImpl final: public Module {
public:
ModuleImpl(ModuleLoader::Impl& loader, kj::String localName, kj::String sourceName)
: loader(loader), localName(kj::mv(localName)), sourceName(kj::mv(sourceName)) {}
ModuleImpl(ModuleLoader::Impl& loader, kj::Own<kj::ReadableFile> file,
kj::ReadableDirectory& sourceDir, kj::PathPtr path)
: loader(loader), file(kj::mv(file)), sourceDir(sourceDir), path(path.clone()),
sourceNameStr(path.toString()) {
KJ_REQUIRE(path.size() > 0);
}
kj::StringPtr getLocalName() {
return localName;
kj::PathPtr getPath() {
return path;
}
kj::StringPtr getSourceName() override {
return sourceName;
return sourceNameStr;
}
Orphan<ParsedFile> loadContent(Orphanage orphanage) override {
kj::Array<const char> content = mmapForRead(localName).releaseAsChars();
kj::Array<const char> content = file->mmap(0, file->stat().size).releaseAsChars();
lineBreaks = nullptr; // In case loadContent() is called multiple times.
lineBreaks = lineBreaksSpace.construct(content);
......@@ -263,17 +87,17 @@ public:
kj::Maybe<Module&> importRelative(kj::StringPtr importPath) override {
if (importPath.size() > 0 && importPath[0] == '/') {
return loader.loadModuleFromSearchPath(importPath.slice(1));
return loader.loadModuleFromSearchPath(kj::Path::parse(importPath.slice(1)));
} else {
return loader.loadModule(catPath(localName, importPath), catPath(sourceName, importPath));
return loader.loadModule(sourceDir, path.parent().eval(importPath));
}
}
kj::Maybe<kj::Array<const byte>> embedRelative(kj::StringPtr embedPath) override {
if (embedPath.size() > 0 && embedPath[0] == '/') {
return loader.readEmbedFromSearchPath(embedPath.slice(1));
return loader.readEmbedFromSearchPath(kj::Path::parse(embedPath.slice(1)));
} else {
return loader.readEmbed(catPath(localName, embedPath), catPath(sourceName, embedPath));
return loader.readEmbed(sourceDir, path.parent().eval(embedPath));
}
}
......@@ -281,8 +105,8 @@ public:
auto& lines = *KJ_REQUIRE_NONNULL(lineBreaks,
"Can't report errors until loadContent() is called.");
loader.getErrorReporter().addError(
localName, lines.toSourcePos(startByte), lines.toSourcePos(endByte), message);
loader.getErrorReporter().addError(sourceDir, path,
lines.toSourcePos(startByte), lines.toSourcePos(endByte), message);
}
bool hadErrors() override {
......@@ -291,8 +115,10 @@ public:
private:
ModuleLoader::Impl& loader;
kj::String localName;
kj::String sourceName;
kj::Own<kj::ReadableFile> file;
kj::ReadableDirectory& sourceDir;
kj::Path path;
kj::String sourceNameStr;
kj::SpaceFor<LineBreakTable> lineBreaksSpace;
kj::Maybe<kj::Own<LineBreakTable>> lineBreaks;
......@@ -301,35 +127,27 @@ private:
// =======================================================================================
kj::Maybe<Module&> ModuleLoader::Impl::loadModule(
kj::StringPtr localName, kj::StringPtr sourceName) {
kj::String canonicalLocalName = canonicalizePath(localName);
kj::String canonicalSourceName = canonicalizePath(sourceName);
auto iter = modules.find(canonicalLocalName);
kj::ReadableDirectory& dir, kj::PathPtr path) {
auto iter = modules.find(std::make_pair(&dir, path));
if (iter != modules.end()) {
// Return existing file.
return *iter->second;
}
if (access(canonicalLocalName.cStr(), F_OK) < 0) {
KJ_IF_MAYBE(file, dir.tryOpenFile(path)) {
auto module = kj::heap<ModuleImpl>(*this, kj::mv(*file), dir, path);
auto& result = *module;
modules.insert(std::make_pair(std::make_pair(&dir, result.getPath()), kj::mv(module)));
return result;
} else {
// No such file.
return nullptr;
}
auto module = kj::heap<ModuleImpl>(
*this, kj::mv(canonicalLocalName), kj::mv(canonicalSourceName));
auto& result = *module;
modules.insert(std::make_pair(result.getLocalName(), kj::mv(module)));
return result;
}
kj::Maybe<Module&> ModuleLoader::Impl::loadModuleFromSearchPath(kj::StringPtr sourceName) {
for (auto& search: searchPath) {
kj::String candidate = kj::str(search, "/", sourceName);
char* end = canonicalizePath(candidate.begin() + (candidate[0] == '/'));
KJ_IF_MAYBE(module, loadModule(
kj::heapString(candidate.slice(0, end - candidate.begin())), sourceName)) {
kj::Maybe<Module&> ModuleLoader::Impl::loadModuleFromSearchPath(kj::PathPtr path) {
for (auto candidate: searchPath) {
KJ_IF_MAYBE(module, loadModule(*candidate, path)) {
return *module;
}
}
......@@ -337,26 +155,16 @@ kj::Maybe<Module&> ModuleLoader::Impl::loadModuleFromSearchPath(kj::StringPtr so
}
kj::Maybe<kj::Array<const byte>> ModuleLoader::Impl::readEmbed(
kj::StringPtr localName, kj::StringPtr sourceName) {
kj::String canonicalLocalName = canonicalizePath(localName);
kj::String canonicalSourceName = canonicalizePath(sourceName);
if (access(canonicalLocalName.cStr(), F_OK) < 0) {
// No such file.
return nullptr;
kj::ReadableDirectory& dir, kj::PathPtr path) {
KJ_IF_MAYBE(file, dir.tryOpenFile(path)) {
return file->get()->mmap(0, file->get()->stat().size);
}
return mmapForRead(localName);
return nullptr;
}
kj::Maybe<kj::Array<const byte>> ModuleLoader::Impl::readEmbedFromSearchPath(
kj::StringPtr sourceName) {
for (auto& search: searchPath) {
kj::String candidate = kj::str(search, "/", sourceName);
char* end = canonicalizePath(candidate.begin() + (candidate[0] == '/'));
KJ_IF_MAYBE(module, readEmbed(
kj::heapString(candidate.slice(0, end - candidate.begin())), sourceName)) {
kj::Maybe<kj::Array<const byte>> ModuleLoader::Impl::readEmbedFromSearchPath(kj::PathPtr path) {
for (auto candidate: searchPath) {
KJ_IF_MAYBE(module, readEmbed(*candidate, path)) {
return kj::mv(*module);
}
}
......@@ -369,10 +177,12 @@ ModuleLoader::ModuleLoader(GlobalErrorReporter& errorReporter)
: impl(kj::heap<Impl>(errorReporter)) {}
ModuleLoader::~ModuleLoader() noexcept(false) {}
void ModuleLoader::addImportPath(kj::String path) { impl->addImportPath(kj::mv(path)); }
void ModuleLoader::addImportPath(kj::ReadableDirectory& dir) {
impl->addImportPath(dir);
}
kj::Maybe<Module&> ModuleLoader::loadModule(kj::StringPtr localName, kj::StringPtr sourceName) {
return impl->loadModule(localName, sourceName);
kj::Maybe<Module&> ModuleLoader::loadModule(kj::ReadableDirectory& dir, kj::PathPtr path) {
return impl->loadModule(dir, path);
}
} // namespace compiler
......
......@@ -31,6 +31,7 @@
#include <kj/memory.h>
#include <kj/array.h>
#include <kj/string.h>
#include <kj/filesystem.h>
namespace capnp {
namespace compiler {
......@@ -44,13 +45,12 @@ public:
~ModuleLoader() noexcept(false);
void addImportPath(kj::String path);
void addImportPath(kj::ReadableDirectory& dir);
// Add a directory to the list of paths that is searched for imports that start with a '/'.
kj::Maybe<Module&> loadModule(kj::StringPtr localName, kj::StringPtr sourceName);
// Tries to load the module with the given filename. `localName` is the path to the file on
// disk (as you'd pass to open(2)), and `sourceName` is the canonical name it should be given
// in the schema (this is used e.g. to decide output file locations). Often, these are the same.
kj::Maybe<Module&> loadModule(kj::ReadableDirectory& dir, kj::PathPtr path);
// Tries to load a module with the given path inside the given directory. Returns nullptr if the
// file doesn't exist.
private:
class Impl;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment