* Copyright (c) 2024-2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "importPathManager.h"
#include "es2panda.h"
#include <libarkbase/os/filesystem.h>
#include "util/arktsconfig.h"
#include "util/diagnostic.h"
#include "util/diagnosticEngine.h"
#include "generated/diagnostic.h"
#include "parser/ETSparser.h"
#include "parser/program/program.h"
#include "ir/expressions/literals/stringLiteral.h"
#include "compiler/lowering/ets/declGenPhase.h"
#include "libarkfile/class_data_accessor-inl.h"
#include "libarkfile/file-inl.h"
#include "libarkbase/utils/logger.h"
#include "util/es2pandaMacros.h"
#include "util/path.h"
#include "varbinder/ETSBinder.h"
#include "varbinder/TSBinder.h"
#include "varbinder/ASBinder.h"
#include "varbinder/JSBinder.h"
#include <algorithm>
#include <cstdio>
#include <memory>
#include <string_view>
#include <utility>
#ifdef PANDA_TARGET_WINDOWS
#include <io.h>
#endif
#ifdef USE_UNIX_SYSCALL
#include <dirent.h>
#include <sys/types.h>
#include <unistd.h>
#else
#if __has_include(<filesystem>)
#include <filesystem>
namespace fs = std::filesystem;
#elif __has_include(<experimental/filesystem>)
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#endif
#endif
#ifdef _WIN32
#include <windows.h>
#else
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <semaphore.h>
#endif
namespace ark::es2panda::parser {
template <util::ModuleKind KIND, typename VarBinderT>
ProgramAdapter<KIND> *Program::New(const util::ImportInfo &importInfo, public_lib::Context *context)
{
varbinder::VarBinder *actualVB = nullptr;
if constexpr (!std::is_same_v<VarBinderT, void>) {
ES2PANDA_ASSERT(context->parserProgram == nullptr);
actualVB = context->Allocator()->New<VarBinderT>(context);
} else {
actualVB = context->parserProgram->VarBinder();
}
ES2PANDA_ASSERT(actualVB != nullptr);
return context->Allocator()->New<ProgramAdapter<KIND>>(importInfo, context->Allocator(), actualVB);
}
}
namespace ark::es2panda::util {
size_t HandleSpecialSymbols(const std::string &input, std::string &output, const size_t &i)
{
switch (input[i + 1]) {
case 'n':
output.push_back('\n');
return 1;
case '?':
output.push_back('?');
return 1;
case '\'': {
std::string_view pattern = "\\'use static\\'";
if (input.compare(i, pattern.size(), pattern) == 0) {
output += "'use static'";
return (pattern.size() - 1);
}
output.push_back('\'');
return 0;
}
default:
output.push_back(input[i]);
return 0;
}
}
static std::string DeleteEscapeSymbols(const std::string &input)
{
std::string output;
output.reserve(input.size());
size_t skip = 0;
for (size_t i = 0; i < input.size(); ++i) {
if (skip > 0) {
--skip;
continue;
}
if (input[i] == '\\' && i + 1 < input.size()) {
size_t consumed = HandleSpecialSymbols(input, output, i);
skip = consumed;
} else {
output.push_back(input[i]);
}
}
return output;
}
static std::string ExtractMnameFromPandafile(const panda_file::File &pf, const panda_file::File::EntityId &classId)
{
std::string name = utf::Mutf8AsCString(pf.GetStringData(classId).data);
auto type = pandasm::Type::FromDescriptor(name);
type = pandasm::Type(type.GetNameWithoutRank(), type.GetRank());
auto recordName = type.GetPandasmName();
auto etsGlobalSuffix = std::string(".") + std::string(compiler::Signatures::ETS_GLOBAL);
ES2PANDA_ASSERT(Helpers::EndsWith(recordName, etsGlobalSuffix));
auto mname = recordName.substr(0, recordName.size() - etsGlobalSuffix.size());
return mname;
}
const ArkTsConfig &ImportPathManager::ArkTSConfig() const
{
return ctx_.config->options->ArkTSConfig();
}
DiagnosticEngine *ImportPathManager::DE() const
{
return ctx_.diagnosticEngine;
}
parser::Program *ImportPathManager::GetGlobalProgram() const
{
return ctx_.parserProgram;
}
parser::Program *ImportPathManager::GatherImportInfo(parser::Program *importer, ir::StringLiteral *importPath)
{
srcPos_ = importPath->Start();
isDynamic_ = importer->IsDeclForDynamicStaticInterop();
auto importInfo = ResolvePath(importer, *importPath);
if (importInfo.ResolvedSource().empty() || !importInfo.IsValid()) {
ES2PANDA_ASSERT(DE()->IsAnyError());
return nullptr;
}
AddFileDependencies(importer->AbsoluteName().Utf8(), importInfo.ResolvedSource());
AddOutputMatching(importInfo.ResolvedSource(), FormAbcFilePath(importInfo));
LOG(DEBUG, ES2PANDA) << "[" << importer->ModuleInfo().moduleName << "] "
<< "Import " << importPath->ToString() << " resolved to " << importInfo.ResolvedSource();
auto *importedProgram = LookupImportDataAndIntroduceProgram(&importInfo);
if ((importedProgram == importer) && !importer->IsStdLib()) {
DE()->LogDiagnostic(diagnostic::IMPORT_ITSELF, util::DiagnosticMessageParams {importInfo.ResolvedSource()},
srcPos_);
}
return importedProgram;
}
static bool IsRelativePath(std::string_view path)
{
for (std::string_view start : {"./", "../", ".\\", "..\\"}) {
if (Helpers::StartsWith(path, start)) {
return true;
}
}
return false;
}
util::StringView ImportPathManager::ResolvePathAPI(parser::Program *importer, ir::StringLiteral *importPath) const
{
srcPos_ = importPath->Start();
auto importInfo = ResolvePath(importer, *importPath);
auto resolvedPath = UString(importInfo.ResolvedSource(), Context()->Allocator());
return resolvedPath.View();
}
void ImportPathManager::TryMatchStaticResolvedPath(ImportPathManager::ResolvedPathRes *result) const
{
auto paths = ArkTSConfig().Paths().find(result->resolvedPath);
if (paths != ArkTSConfig().Paths().cend()) {
result->resolvedPath = *paths->second.begin();
result->resolvedIsExternalModule = false;
}
}
void ImportPathManager::TryMatchDynamicResolvedPath(ImportPathManager::ResolvedPathRes *result) const
{
auto packagePathPair = ArkTSConfig().SourcePathMap().find(result->resolvedPath);
if (packagePathPair != ArkTSConfig().SourcePathMap().cend()) {
result->resolvedPath = packagePathPair->second;
result->resolvedIsExternalModule = true;
return;
}
auto paths = ArkTSConfig().Paths().find(result->resolvedPath);
if (paths != ArkTSConfig().Paths().cend()) {
result->resolvedPath = *paths->second.begin();
result->resolvedIsExternalModule = false;
}
}
ImportInfo ImportPathManager::ResolvePath(parser::Program *importer, std::string_view importPath) const
{
if (importPath.empty()) {
DE()->LogDiagnostic(diagnostic::EMPTY_IMPORT_PATH, util::DiagnosticMessageParams {});
return {};
}
ResolvedPathRes result {};
if (IsRelativePath(importPath)) {
auto curModulePath = isDynamic_ ? importer->GetImportInfo().ResolvedSource() : importer->AbsoluteName().Utf8();
size_t pos = curModulePath.find_last_of("/\\");
auto currentDir = (pos != std::string::npos) ? curModulePath.substr(0, pos) : curModulePath;
std::string resolvedPathPrototype {currentDir};
resolvedPathPrototype += pathDelimiter_;
resolvedPathPrototype += importPath;
result = AppendExtensionOrIndexFileIfOmitted(resolvedPathPrototype);
if (result.resolvedIsExternalModule) {
TryMatchStaticResolvedPath(&result);
} else {
TryMatchDynamicResolvedPath(&result);
}
} else {
result = ResolveAbsolutePath(importPath);
}
return {*this, std::string(result.resolvedPath), ToLanguage(importer->Extension()).GetId(),
result.resolvedIsExternalModule};
}
ImportPathManager::ResolvedPathRes ImportPathManager::ResolveAbsolutePath(std::string_view importPath) const
{
ES2PANDA_ASSERT(!IsRelativePath(importPath));
if (importPath.at(0) == pathDelimiter_.at(0)) {
std::string resolvedPathPrototype = ArkTSConfig().BaseUrl();
resolvedPathPrototype += importPath;
return AppendExtensionOrIndexFileIfOmitted(resolvedPathPrototype);
}
auto resolvedPath = ArkTSConfig().ResolvePath(importPath, isDynamic_);
if (!resolvedPath) {
DE()->LogDiagnostic(
diagnostic::IMPORT_CANT_FIND_PREFIX,
util::DiagnosticMessageParams {util::StringView(importPath), util::StringView(ArkTSConfig().ConfigPath())},
srcPos_);
return {""};
}
return AppendExtensionOrIndexFileIfOmitted(resolvedPath.value());
}
parser::PackageProgram *ImportPathManager::NewEmptyPackage(const ImportInfo &importInfo)
{
auto allocator = Context()->allocator;
auto package = parser::Program::New<ModuleKind::PACKAGE>(importInfo, Context());
package->SetPackageInfo(importInfo.ModuleName(), util::ModuleKind::PACKAGE);
auto ident = allocator->New<ir::Identifier>(compiler::Signatures::ETS_GLOBAL, allocator);
ArenaVector<ir::Statement *> stmts(allocator->Adapter());
auto etsModule = allocator->New<ir::ETSModule>(allocator, std::move(stmts), ident, ir::ModuleFlag::ETSSCRIPT,
Language::Id::ETS, package);
package->SetAst(etsModule);
return package;
}
std::string GetRealPath(const std::string &path)
{
return ark::os::GetAbsolutePath(path);
}
template <typename VarBinderT, Language::Id LANG_ID>
void ImportPathManager::SetupGlobalProgram(public_lib::Context *ctx)
{
ES2PANDA_ASSERT(Context()->config->options->GetCompilationMode() != CompilationMode::GEN_STD_LIB);
auto normalizedPathForGlobalProg = GetRealPath(std::string(ctx->sourceFile->filePath));
if (normalizedPathForGlobalProg.empty()) {
normalizedPathForGlobalProg = ark::os::NormalizePath(std::string(ctx->sourceFile->filePath));
}
if constexpr (LANG_ID == Language::Id::ETS) {
util::ImportInfo importInfo {*this, normalizedPathForGlobalProg};
importInfo.SetData<ModuleKind::MODULE>(normalizedPathForGlobalProg, std::string(ctx->input));
ctx->parserProgram = IntroduceProgram<ModuleKind::MODULE, VarBinderT>(importInfo);
AddOutputMatching(normalizedPathForGlobalProg, FormAbcFilePath(ctx->parserProgram->GetImportInfo()));
} else {
util::ImportInfo importInfo {};
importInfo.moduleName_ = normalizedPathForGlobalProg;
importInfo.lang_ = LANG_ID;
importInfo.SetData<ModuleKind::MODULE>(normalizedPathForGlobalProg, std::string(ctx->input));
ctx->parserProgram = IntroduceProgram<ModuleKind::MODULE, VarBinderT>(importInfo);
}
if (Context()->config->options->IsEtsUnnamed()) {
ctx->parserProgram->SetPackageInfo("", util::ModuleKind::MODULE);
}
}
static constexpr auto STDLIB_MAIN_PROG_NAME = "etsstdlib.ets";
static constexpr auto STDLIB_IMPORTS_MAIN_PROG_NAME = "<default_import>.ets";
static constexpr auto SIMULT_MAIN_PROG_NAME = "<simult>";
void ImportPathManager::SetupGlobalProgram()
{
if (Context()->config->options->GetCompilationMode() == CompilationMode::GEN_STD_LIB) {
ES2PANDA_ASSERT(Context()->config->options->GetExtension() == ScriptExtension::ETS);
util::ImportInfo importInfo {*this, STDLIB_MAIN_PROG_NAME};
importInfo.SetData<ModuleKind::MODULE, false>(STDLIB_MAIN_PROG_NAME, "");
Context()->parserProgram = IntroduceProgram<ModuleKind::MODULE, varbinder::ETSBinder>(importInfo);
return;
}
switch (Context()->config->options->GetExtension()) {
case ScriptExtension::TS:
return SetupGlobalProgram<varbinder::TSBinder, Language::Id::TS>(Context());
case ScriptExtension::AS:
return SetupGlobalProgram<varbinder::ASBinder, Language::Id::AS>(Context());
case ScriptExtension::ETS:
return SetupGlobalProgram<varbinder::ETSBinder, Language::Id::ETS>(Context());
case ScriptExtension::JS:
return SetupGlobalProgram<varbinder::JSBinder, Language::Id::JS>(Context());
default:
ES2PANDA_UNREACHABLE();
}
}
static ArenaString OhmurlToMname(ArenaString &&ohmurl)
{
std::replace(ohmurl.begin(), ohmurl.end(), '\\', '/');
ES2PANDA_ASSERT(std::find(ohmurl.begin(), ohmurl.end(), '\\') == ohmurl.end());
if (ohmurl.at(0) == '/') {
ohmurl.erase(0, 1);
}
ArenaString mnamePrototype {std::move(ohmurl)};
size_t start = 0;
if (!mnamePrototype.empty() && mnamePrototype[0] == '@') {
size_t slashPos = mnamePrototype.find('/');
if (slashPos != ArenaString::npos) {
start = slashPos + 1;
}
}
std::replace(mnamePrototype.begin() + start, mnamePrototype.end(), '/', '.');
return mnamePrototype;
}
static ArenaString OhmurlToMname(std::string_view ohmurl)
{
return OhmurlToMname(ArenaString {ohmurl});
}
parser::Program *ImportPathManager::SetupProgramForDebugInfoPlugin(std::string_view sourceFilePath,
[[maybe_unused]] std::string_view moduleName)
{
util::ImportInfo importInfo {
*this,
std::string(sourceFilePath),
};
importInfo.SetData<ModuleKind::MODULE, false>(std::string(sourceFilePath), "");
auto *program = IntroduceProgram<ModuleKind::MODULE>(importInfo);
program->SetSource({sourceFilePath, "", "", true, false});
ES2PANDA_ASSERT(importInfo.ModuleName() == moduleName);
auto allocator = Context()->Allocator();
auto *emptyIdent = allocator->New<ir::Identifier>("", allocator);
auto *etsModule = allocator->New<ir::ETSModule>(allocator, ArenaVector<ir::Statement *>(allocator->Adapter()),
emptyIdent, ir::ModuleFlag::ETSSCRIPT, importInfo.Lang(), program);
program->SetAst(etsModule);
Context()->parserProgram->GetExternalDecls()->Add(program);
return program;
}
parser::Program *ImportPathManager::IntroduceStdlibImportProgram(std::string &&contents)
{
util::ImportInfo importInfo {*this, STDLIB_IMPORTS_MAIN_PROG_NAME};
importInfo.SetData<ModuleKind::MODULE>(STDLIB_IMPORTS_MAIN_PROG_NAME, std::move(contents));
if (auto *existing = SearchResolved(importInfo); existing != nullptr) {
return existing;
}
return IntroduceProgram<ModuleKind::MODULE>(importInfo);
}
void ImportPathManager::IntroduceMainProgramForSimult()
{
ES2PANDA_ASSERT(Context()->parserProgram == nullptr);
util::ImportInfo importInfo {*this, SIMULT_MAIN_PROG_NAME};
importInfo.SetData<ModuleKind::SIMULT_MAIN, false>(SIMULT_MAIN_PROG_NAME, "");
auto program = IntroduceProgram<ModuleKind::SIMULT_MAIN, varbinder::ETSBinder>(importInfo);
auto allocator = Context()->allocator;
auto ident = allocator->New<ir::Identifier>(compiler::Signatures::ETS_GLOBAL, allocator);
ArenaVector<ir::Statement *> stmts(allocator->Adapter());
auto etsModule = allocator->New<ir::ETSModule>(allocator, std::move(stmts), ident, ir::ModuleFlag::ETSSCRIPT,
Language::Id::ETS, program);
program->SetAst(etsModule);
Context()->parserProgram = program;
}
void ImportPathManager::PrepareParseQueueForProgram(parser::Program *program)
{
ES2PANDA_ASSERT(program != nullptr);
ClearParseList();
GetFileDependencies().erase(ArenaString {program->AbsoluteName().Utf8()});
parseQueue_.emplace_back(ParseInfo {false, program});
srcPos_.SetProgram(program);
}
static bool IsExtensionForPackageFraction(const std::string &extension)
{
return extension == ImportPathManager::ETS_SUFFIX;
}
parser::PackageProgram *ImportPathManager::RegisterSourcesForPackageFromGlobbedDirectory(const ImportInfo &importInfo)
{
ES2PANDA_ASSERT(importInfo.PointsToPackage());
ES2PANDA_ASSERT(LookupProgramCaches(importInfo) == nullptr);
auto *package = NewEmptyPackage(importInfo);
#ifdef USE_UNIX_SYSCALL
UnixRegisterSourcesForPackageFromGlobbedDirectory(package, importInfo);
#else
for (auto const &entry : fs::directory_iterator(std::string(importInfo.ResolvedSource()))) {
if (!fs::is_regular_file(entry) || !IsExtensionForPackageFraction(entry.path().extension().string())) {
continue;
}
ImportInfo globElemImportInfo {*this, entry.path().string(), Language::Id::ETS};
RegisterPackageFraction(package, &globElemImportInfo);
}
#endif
return package;
}
#ifdef USE_UNIX_SYSCALL
void ImportPathManager::UnixRegisterSourcesForPackageFromGlobbedDirectory(parser::PackageProgram *package,
const ImportInfo &importInfo)
{
const auto directoryPath = std::string(importInfo.ResolvedSource());
DIR *dir = opendir(directoryPath.c_str());
if (dir == nullptr) {
DE()->LogDiagnostic(diagnostic::OPEN_FOLDER_FAILED, util::DiagnosticMessageParams {directoryPath}, srcPos_);
return;
}
struct dirent *entry;
while ((entry = readdir(dir)) != nullptr) {
if (entry->d_type != DT_REG) {
continue;
}
std::string fileName = entry->d_name;
std::string::size_type pos = fileName.find_last_of('.');
if (pos == std::string::npos || !IsExtensionForPackageFraction(fileName.substr(pos))) {
continue;
}
std::string filePath = directoryPath + "/" + entry->d_name;
ImportInfo globElemImportInfo {*this, filePath, Language::Id::ETS};
RegisterPackageFraction(package, &globElemImportInfo);
}
closedir(dir);
return;
}
#endif
void ImportInfo::LinkFractionInfoToPackage(const parser::PackageProgram &package)
{
moduleName_ = package.ModuleName();
}
inline Span<const uint8_t> ImportInfo::GetMetadata(const panda_file::File &pf)
{
return pf.GetMetadata();
}
void ImportPathManager::RegisterPackageFraction(parser::PackageProgram *package, ImportInfo *importInfo)
{
auto *fraction = SearchResolved(*importInfo);
if (fraction == GetGlobalProgram()) {
return;
}
if (fraction == nullptr) {
ES2PANDA_ASSERT(!importInfo->ResolvedPathIsVirtual());
importInfo->SetTextFile<ModuleKind::MODULE, false>(std::string(importInfo->ResolvedSource()), DE());
fraction = IntroduceProgram<ModuleKind::MODULE>(*importInfo);
} else {
auto &modules = GetGlobalProgram()->GetExternalDecls()->Get<ModuleKind::MODULE>();
auto newEndIt = std::remove(modules.begin(), modules.end(), fraction);
modules.erase(newEndIt, modules.end());
}
const_cast<ImportInfo *>(&fraction->GetImportInfo())->LinkFractionInfoToPackage(*package);
fraction->SetPackageInfo(package->ModuleName(), ModuleKind::PACKAGE);
ES2PANDA_ASSERT(fraction->ModuleName().find(package->ModuleName()) == 0);
package->AppendFraction(fraction->As<ModuleKind::MODULE>());
}
parser::Program *ImportPathManager::LookupProgramCaches(const ImportInfo &importInfo)
{
if (Context()->globalContext == nullptr) {
return nullptr;
}
parser::Program *cachedProg = nullptr;
auto key = std::string(importInfo.Key());
const auto &cachedExtProgs = Context()->globalContext->cachedExternalPrograms;
if (cachedExtProgs.find(key) != cachedExtProgs.end()) {
cachedProg = cachedExtProgs.at(key);
ES2PANDA_ASSERT(key == cachedProg->GetImportInfo().Key());
} else if (auto *cachedStdlib = Context()->globalContext->stdLibAstCache; cachedStdlib != nullptr) {
cachedStdlib->Visit<false>([&cachedProg, &key](auto *prog) {
if (prog->GetImportInfo().Key() == key) {
cachedProg = prog;
}
});
}
if (cachedProg != nullptr) {
RegisterProgram(cachedProg);
}
return cachedProg;
}
std::string ImportPathManager::FormEtscacheFilePath(std::string moduleName, const std::string &cacheDir)
{
ES2PANDA_ASSERT(!cacheDir.empty());
ES2PANDA_ASSERT(!moduleName.empty());
std::replace(moduleName.begin(), moduleName.end(), '.', util::Path::GetPathDelimiter());
return cacheDir + util::PATH_DELIMITER + moduleName + std::string {CACHE_SUFFIX};
}
std::string ImportPathManager::FormAbcFilePath([[maybe_unused]] const ImportInfo &imd) const
{
#if not defined PANDA_TARGET_MOBILE
std::string outputDir;
ES2PANDA_ASSERT(Context() != nullptr && Context()->config != nullptr);
auto *opts = Context()->config->options;
if (opts != nullptr && opts->WasSetOutput()) {
if (!opts->IsIncremental()) {
ES2PANDA_ASSERT(!ark::os::file::File::IsDirectory(opts->GetOutput()));
return fs::absolute(opts->GetOutput()).string();
}
if (!ark::os::file::File::IsDirectory(opts->GetOutput())) {
DE()->LogDiagnostic(diagnostic::SIMULTANEOUS_INCREMENTAL_OUTPUT, util::DiagnosticMessageParams {});
return "";
}
outputDir = fs::absolute(opts->GetOutput()).string();
} else if (Context()->emitter != nullptr && !Context()->emitter->IsETSEmitter()) {
return fs::absolute(std::string {ImportInfo::DUMMY_PATH} + std::string {ImportPathManager::ABC_SUFFIX})
.string();
} else if (!opts->ArkTSConfig().CacheDir().empty()) {
outputDir = fs::absolute(opts->ArkTSConfig().CacheDir()).string();
} else {
outputDir = fs::absolute("build").string();
}
std::string abcFile = outputDir;
abcFile += util::PATH_DELIMITER;
abcFile += imd.ModuleName();
abcFile += ABC_SUFFIX;
return abcFile;
#endif
return "";
}
class EtscacheFileLock {
public:
NO_MOVE_SEMANTIC(EtscacheFileLock);
NO_COPY_SEMANTIC(EtscacheFileLock);
EtscacheFileLock(std::string dstDeclPath, std::string srcAbcPath)
: dstPath_(std::move(dstDeclPath)), abcPath_(std::move(srcAbcPath))
{
if ((!os::IsFileExists(dstPath_) || ShouldRewrite(abcPath_, dstPath_))) {
writer_ = ExlusiveFileWriter::Open(dstPath_);
} else {
ExlusiveFileWriter::WaitUnlockForRead(dstPath_);
}
}
~EtscacheFileLock() = default;
void WriteEtscacheFile(std::string_view text) const
{
ES2PANDA_ASSERT(bool(writer_));
writer_->Write(text);
}
bool ShouldWriteDeclfile() const
{
return bool(writer_);
}
private:
static bool ShouldRewrite([[maybe_unused]] const std::string &src, [[maybe_unused]] const std::string &dst)
{
#ifdef USE_UNIX_SYSCALL
return true;
#else
return fs::last_write_time(src) > fs::last_write_time(dst);
#endif
}
class ExlusiveFileWriter {
protected:
#ifdef PANDA_TARGET_WINDOWS
using FD = HANDLE;
#else
using FD = int;
#endif
public:
using Pointer = std::unique_ptr<ExlusiveFileWriter>;
NO_COPY_SEMANTIC(ExlusiveFileWriter);
DEFAULT_MOVE_SEMANTIC(ExlusiveFileWriter);
static void WaitUnlockForRead(const std::string &filename)
{
FlockTrace(filename, "Waiting RO unlock");
#ifdef PANDA_TARGET_WINDOWS
auto const fd =
::CreateFileW(Utf8ToWString(filename).c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (fd == INVALID_HANDLE_VALUE) {
std::cerr << "File opening error '" << filename << "': " << GetErrorMessage(::GetLastError())
<< std::endl;
return;
}
OVERLAPPED ov = {};
if (::LockFileEx(fd, 0, 0, MAXDWORD, MAXDWORD, &ov)) {
::UnlockFileEx(fd, 0, MAXDWORD, MAXDWORD, &ov);
FlockTrace(filename, "Unlock");
} else {
FlockTrace(filename, std::string {"- Waiting error '"} + GetErrorMessage(::GetLastError()) + "'");
}
::CloseHandle(fd);
#else
auto const fd = ::open(filename.c_str(), O_RDONLY | O_CLOEXEC);
if (fd == -1) {
std::cerr << "File opening error '" << filename << "': " << ::strerror(errno) << std::endl;
return;
}
if (::flock(fd, LOCK_EX) != 0) {
FlockTrace(filename, std::string {"- Waiting error '"} + ::strerror(errno) + "'");
} else {
::flock(fd, LOCK_UN);
FlockTrace(filename, "Unlock");
}
::close(fd);
#endif
}
static Pointer Open(const std::string &filename)
{
#ifdef USE_UNIX_SYSCALL
return {};
#else
const std::string absDecl = fs::absolute(filename).string();
fs::create_directories(fs::path(absDecl).parent_path());
FlockTrace(filename, "Open for write");
#endif
#ifdef PANDA_TARGET_WINDOWS
FD fd = ::CreateFileW(Utf8ToWString(filename).c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (fd == INVALID_HANDLE_VALUE) {
std::cerr << "File opening error '" << filename << "': " << GetErrorMessage(::GetLastError())
<< std::endl;
return {};
}
OVERLAPPED ov = {};
if (!::LockFileEx(fd, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, MAXDWORD, MAXDWORD, &ov)) {
FlockTrace(filename, "- Waiting");
if (!::LockFileEx(fd, LOCKFILE_EXCLUSIVE_LOCK, 0, MAXDWORD, MAXDWORD, &ov)) {
FlockTrace(filename, std::string {"-- Waiting error '"} + GetErrorMessage(::GetLastError()) + "'");
}
FlockTrace(filename, "Close (skip)");
::UnlockFileEx(fd, 0, MAXDWORD, MAXDWORD, &ov);
::CloseHandle(fd);
return {};
}
#else
auto constexpr CHMOD = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
auto const fd = ::open(filename.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, CHMOD);
if (fd == -1) {
std::cerr << "File opening error '" << filename << "': " << ::strerror(errno) << std::endl;
return {};
}
if (::flock(fd, LOCK_EX | LOCK_NB) != 0) {
FlockTrace(filename, "- Waiting");
if (::flock(fd, LOCK_EX) != 0) {
FlockTrace(filename, std::string {"-- Waiting error '"} + ::strerror(errno) + "'");
}
FlockTrace(filename, "Close (skip)");
::flock(fd, LOCK_UN);
::close(fd);
return {};
}
#endif
FlockTrace(filename, "- Locked");
return Pointer {new ExlusiveFileWriter(fd, filename)};
}
void Write(std::string_view text)
{
FlockTrace("- Write");
ES2PANDA_ASSERT(!written_);
#ifdef PANDA_TARGET_WINDOWS
DWORD bytesWritten = 0;
if (!::WriteFile(fd_, text.data(), text.size(), &bytesWritten, NULL)) {
std::cerr << "Error writing to the file '" << filename_ << "': " << GetErrorMessage(::GetLastError())
<< std::endl;
} else {
written_ = true;
}
#else
if (::write(fd_, text.data(), text.size()) == -1) {
std::cerr << "Error writing to the file '" << filename_ << "': " << ::strerror(errno) << std::endl;
} else {
written_ = true;
}
#endif
}
~ExlusiveFileWriter()
{
FlockTrace("Close");
#ifdef PANDA_TARGET_WINDOWS
if (written_) {
::SetEndOfFile(fd_);
}
OVERLAPPED ov = {};
if (!::UnlockFileEx(fd_, 0, MAXDWORD, MAXDWORD, &ov)) {
std::cerr << "File truncate error '" << filename_ << "': " << GetErrorMessage(::GetLastError())
<< std::endl;
}
::CloseHandle(fd_);
#else
if (written_) {
auto const curPos = ::lseek(fd_, 0, SEEK_CUR);
if (::ftruncate(fd_, curPos) != 0) {
std::cerr << "File truncate error '" << filename_ << "': " << ::strerror(errno) << std::endl;
}
}
::flock(fd_, LOCK_UN);
::close(fd_);
#endif
}
private:
explicit ExlusiveFileWriter(FD fd, std::string filename) : fd_(fd), filename_(std::move(filename)) {}
#ifdef PANDA_TARGET_WINDOWS
static std::string GetErrorMessage(DWORD errorMessageID)
{
if (errorMessageID == 0) {
return {};
}
LPSTR messageBuffer = nullptr;
size_t const size = ::FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPSTR>(&messageBuffer), 0, NULL);
std::string message(messageBuffer, size);
::LocalFree(messageBuffer);
return message;
}
static std::wstring Utf8ToWString(const std::string_view &str)
{
if (str.empty()) {
return {};
}
auto const size_needed = ::MultiByteToWideChar(CP_UTF8, 0, str.data(), int(str.size()), NULL, 0);
auto result = std::wstring(size_t(size_needed), L'\0');
::MultiByteToWideChar(CP_UTF8, 0, str.data(), int(str.size()), result.data(), size_needed);
return result;
}
#endif
inline static void FlockTrace(const std::string &filename, const std::string &msg)
{
(void)filename;
(void)msg;
#ifdef IPM_FLOCK_DEBUG_TRACE
auto const now = std::chrono::steady_clock::now();
auto const ticks = now.time_since_epoch().count();
std::cerr << "#FLOCK:" << '\t' << ticks << '\t' << ::getpid() << '\t' << msg << '\t' << filename
<< std::endl;
#endif
}
inline void FlockTrace(const std::string &msg) const
{
FlockTrace(filename_, msg);
}
private:
FD fd_;
std::string filename_;
bool written_ = false;
};
private:
std::string dstPath_;
std::string abcPath_;
ExlusiveFileWriter::Pointer writer_ = {};
};
void ImportPathManager::ExtractEtscacheToFile(const panda_file::File &pf, const std::string &abcPath,
const std::string &cacheDir)
{
for (auto id : pf.GetExported()) {
panda_file::File::EntityId classId(id);
auto mname = ExtractMnameFromPandafile(pf, classId);
std::string dstPath {ImportPathManager::FormEtscacheFilePath(mname, cacheDir)};
if (EtscacheFileLock lock {dstPath, abcPath}; lock.ShouldWriteDeclfile()) {
std::stringstream ss;
panda_file::ClassDataAccessor {pf, classId}.EnumerateAnnotation(
ImportPathManager::ANNOTATION_MODULE_DECLARATION.data(),
[&pf, &ss](panda_file::AnnotationDataAccessor &annotationAccessor) {
auto elemDeclaration = annotationAccessor.GetElement(0);
auto valueDeclaration = elemDeclaration.GetScalarValue();
const auto idAnnoDeclaration = valueDeclaration.Get<panda_file::File::EntityId>();
ss << panda_file::StringDataToString(pf.GetStringData(idAnnoDeclaration));
return true;
});
std::string declText = ss.str();
if (!declText.empty()) {
auto processed = DeleteEscapeSymbols(declText);
lock.WriteEtscacheFile(processed);
}
}
}
}
parser::Program *ImportPathManager::IntroduceProgram(const ImportInfo &importInfo)
{
switch (importInfo.Data().Kind()) {
case ModuleKind::MODULE:
return IntroduceProgram<ModuleKind::MODULE>(importInfo);
case ModuleKind::SOURCE_DECL:
return IntroduceProgram<ModuleKind::SOURCE_DECL>(importInfo);
case ModuleKind::ETSCACHE_DECL:
return IntroduceProgram<ModuleKind::ETSCACHE_DECL>(importInfo);
case ModuleKind::DECLLESS_DYNAMIC:
return IntroduceProgram<ModuleKind::DECLLESS_DYNAMIC>(importInfo);
case ModuleKind::PACKAGE:
return IntroduceProgram<ModuleKind::PACKAGE>(importInfo);
case ModuleKind::METADATA_DECL:
if (!ctx_.config->options->IsReadMetadata()) {
DE()->LogDiagnostic(diagnostic::UNSUPPORTED_IMPORT_WITH_METADATA,
DiagnosticMessageParams {importInfo.AbcPath()});
return nullptr;
}
ES2PANDA_ASSERT(importInfo.ReferencesABC());
return IntroduceProgram<ModuleKind::METADATA_DECL>(importInfo);
default: {
ES2PANDA_ASSERT(DE()->IsAnyError());
return nullptr;
}
}
}
template <ModuleKind KIND, typename VarBinderT>
parser::ProgramAdapter<KIND> *ImportPathManager::IntroduceProgram(const ImportInfo &importInfo)
{
ES2PANDA_ASSERT(importInfo.Data().Kind() == KIND);
parser::ProgramAdapter<KIND> *newProg = nullptr;
if constexpr (KIND == ModuleKind::PACKAGE) {
newProg = RegisterSourcesForPackageFromGlobbedDirectory(importInfo);
} else {
newProg = parser::Program::New<KIND, VarBinderT>(importInfo, Context());
ES2PANDA_ASSERT(!importInfo.ModuleName().empty());
newProg->SetPackageInfo(importInfo.ModuleName(), KIND);
}
RegisterProgram(newProg);
return newProg;
}
class ImportPathManager::ResolvedSources {
public:
explicit ResolvedSources(ImportPathManager *ipm) : ipm_ {ipm} {}
void Register(parser::Program *program, DiagnosticEngine *de)
{
ArenaString key {program->GetImportInfo().Key()};
ES2PANDA_ASSERT(progsByResolvedPath_.find(key) == progsByResolvedPath_.end());
progsByResolvedPath_[key] = program;
if (program->Is<ModuleKind::PACKAGE>()) {
progsByResolvedPath_[ArenaString {program->ModuleName()}] = program;
}
ArenaString mname {program->GetImportInfo().ModuleName()};
auto &modulePrograms = modules_[mname];
if (program->IsDeclarationModule()) {
auto &declProgs = modulePrograms.declProgs;
ES2PANDA_ASSERT(std::find(declProgs.begin(), declProgs.end(), program) == declProgs.end());
declProgs.push_back(program);
if (modulePrograms.implProg != nullptr) {
progsByResolvedPath_.at(key) = modulePrograms.implProg;
}
return;
}
if (modulePrograms.implProg != nullptr) {
ES2PANDA_ASSERT(modulePrograms.implProg != program);
auto arkuiIndexFileClashException = util::Helpers::EndsWith(mname, ".index");
if (arkuiIndexFileClashException) {
return;
}
auto absPath1 = program->GetImportInfo().TextSource();
auto absPath2 = modulePrograms.implProg->GetImportInfo().TextSource();
de->LogDiagnostic(diagnostic::FILE_RUNTIME_NAME_CLASH,
util::DiagnosticMessageParams {absPath1, absPath2, mname});
}
modulePrograms.implProg = program;
if (ipm_->GetGlobalProgram() != nullptr) {
auto &extProgramsDecls = ipm_->GetGlobalProgram()->GetExternalDecls()->Get<ModuleKind::SOURCE_DECL>();
auto extProgramsDeclsNewEnd = extProgramsDecls.end();
for (auto *declProg : modulePrograms.declProgs) {
progsByResolvedPath_.at(ArenaString {declProg->GetImportInfo().Key()}) = program;
extProgramsDeclsNewEnd = std::remove(extProgramsDecls.begin(), extProgramsDeclsNewEnd, declProg);
}
extProgramsDecls.erase(extProgramsDeclsNewEnd, extProgramsDecls.end());
}
}
parser::Program *SearchResolved(const ImportInfo &importInfo) const
{
if (auto it = progsByResolvedPath_.find(importInfo.Key()); it != progsByResolvedPath_.end()) {
ES2PANDA_ASSERT(it->second != nullptr);
return it->second;
}
return nullptr;
}
void MaybeAddToExternalSources(parser::Program *newProg, parser::Program::ExternalDecls *extDecls)
{
auto *globalProgram = ipm_->GetGlobalProgram();
if (newProg == globalProgram) {
return;
}
[[maybe_unused]] bool isPackageFraction =
(newProg->ModuleInfo().kind == ModuleKind::PACKAGE) && newProg->Is<ModuleKind::MODULE>();
ES2PANDA_ASSERT(!isPackageFraction);
if (auto pointedProgram = SearchResolved(newProg->GetImportInfo()); pointedProgram == newProg) {
auto alreadyInExternalSources = [newProg, extDecls]() -> bool {
switch (newProg->GetModuleKind()) {
case ModuleKind::MODULE: {
const auto &programs = extDecls->Get<ModuleKind::MODULE>();
return std::find(programs.begin(), programs.end(), newProg) != programs.end();
}
case ModuleKind::SOURCE_DECL: {
const auto &programs = extDecls->Get<ModuleKind::SOURCE_DECL>();
return std::find(programs.begin(), programs.end(), newProg) != programs.end();
}
case ModuleKind::PACKAGE: {
const auto &programs = extDecls->Get<ModuleKind::PACKAGE>();
return std::find(programs.begin(), programs.end(), newProg) != programs.end();
}
case ModuleKind::ETSCACHE_DECL: {
const auto &programs = extDecls->Get<ModuleKind::ETSCACHE_DECL>();
return std::find(programs.begin(), programs.end(), newProg) != programs.end();
}
case ModuleKind::METADATA_DECL: {
const auto &programs = extDecls->Get<ModuleKind::METADATA_DECL>();
return std::find(programs.begin(), programs.end(), newProg) != programs.end();
}
default:
return false;
}
};
if (alreadyInExternalSources()) {
return;
}
extDecls->Add(newProg);
} else {
[[maybe_unused]] const auto &imd = newProg->GetImportInfo();
ES2PANDA_ASSERT((imd.Kind() == ModuleKind::SOURCE_DECL) || (imd.Kind() == ModuleKind::ETSCACHE_DECL));
ES2PANDA_ASSERT(!pointedProgram->IsDeclarationModule());
}
}
parser::PackageProgram *FixupPackageByFraction(parser::Program *fractionBeingParsed, const ArenaString &packageName)
{
if (progsByResolvedPath_.count(packageName) != 0) {
auto *pkg = progsByResolvedPath_.at(packageName)->As<ModuleKind::PACKAGE>();
auto *pointeeProg = ipm_->SearchResolved(fractionBeingParsed->GetImportInfo());
if (pointeeProg == fractionBeingParsed) {
ArenaString key {fractionBeingParsed->GetImportInfo().Key()};
progsByResolvedPath_[key] = pkg;
}
ES2PANDA_ASSERT(ipm_->SearchResolved(fractionBeingParsed->GetImportInfo()) == pkg);
return pkg;
}
const_cast<ImportInfo *>(&fractionBeingParsed->GetImportInfo())->moduleName_ = packageName;
fractionBeingParsed->SetPackageInfo(packageName, util::ModuleKind::PACKAGE);
ImportInfo pkgMetadata {};
pkgMetadata.resolvedSource_ = packageName;
pkgMetadata.moduleName_ = packageName;
pkgMetadata.SetData<ModuleKind::PACKAGE, false>(std::string(packageName), "");
auto newPkg = ipm_->NewEmptyPackage(pkgMetadata);
newPkg->AppendFraction(fractionBeingParsed->As<ModuleKind::MODULE>());
auto &modulePrograms = ipm_->GetGlobalProgram()->GetExternalDecls()->Get<ModuleKind::MODULE>();
auto newEndIt = std::remove(modulePrograms.begin(), modulePrograms.end(), fractionBeingParsed);
if (newEndIt != modulePrograms.end()) {
modulePrograms.erase(newEndIt, modulePrograms.end());
ipm_->GetGlobalProgram()->GetExternalDecls()->Add(newPkg);
} else {
ES2PANDA_ASSERT(ipm_->GetGlobalProgram() == fractionBeingParsed);
}
ES2PANDA_ASSERT(progsByResolvedPath_.find(packageName) == progsByResolvedPath_.end());
progsByResolvedPath_.find(fractionBeingParsed->GetImportInfo().Key())->second = newPkg;
progsByResolvedPath_[packageName] = newPkg;
ES2PANDA_ASSERT(ipm_->SearchResolved(fractionBeingParsed->GetImportInfo()) == newPkg);
return newPkg;
}
void RemoveProgramFromResolvedSources(ArenaString filename)
{
progsByResolvedPath_.erase(filename);
modules_.erase(filename);
}
private:
ImportPathManager *ipm_ {};
ArenaMap<ArenaString, parser::Program *, CompareByLength> progsByResolvedPath_;
struct Module {
parser::Program *implProg {};
ArenaVector<parser::Program *> declProgs {};
};
ArenaMap<ArenaString, Module, CompareByLength> modules_;
};
void ImportPathManager::InitParseQueueForSimult()
{
ES2PANDA_ASSERT(GetParseQueue().empty());
IntroduceMainProgramForSimult();
srcPos_.SetProgram(Context()->parserProgram);
ES2PANDA_ASSERT(Context()->config->options->GetCompilationMode() >= CompilationMode::SIMULTANEOUS);
ES2PANDA_ASSERT(Context()->config->options->GetExtension() == ScriptExtension::ETS);
for (auto &sourceName : Context()->sourceFileNames) {
util::ImportInfo importInfo {*this, sourceName};
importInfo.SetTextFile<ModuleKind::MODULE>(std::string(sourceName), DE());
auto *program = IntroduceProgram(importInfo);
resolvedSources_.MaybeAddToExternalSources(program, GetGlobalProgram()->GetExternalDecls());
program->SetIsBuiltSimultaneously();
}
}
void ImportPathManager::RegisterProgram(parser::Program *program)
{
if (program == nullptr) {
ES2PANDA_ASSERT(DE()->IsAnyError());
return;
}
resolvedSources_.Register(program, DE());
switch (program->GetModuleKind()) {
case ModuleKind::PACKAGE:
case ModuleKind::SIMULT_MAIN:
case ModuleKind::DECLLESS_DYNAMIC:
case ModuleKind::METADATA_DECL:
return;
default: {
bool isParsed = program->Ast() != nullptr;
parseQueue_.emplace_back(ParseInfo {isParsed, program});
}
}
}
parser::Program *ImportPathManager::SearchResolved(const ImportInfo &importInfo) const
{
return resolvedSources_.SearchResolved(importInfo);
}
void ImportPathManager::RemoveProgramFromResolvedSources(ArenaString filename)
{
resolvedSources_.RemoveProgramFromResolvedSources(filename);
}
parser::Program *ImportPathManager::FindOrIntroduceProgramForIncremental(std::string_view absolutePath)
{
return FindOrIntroduceProgram<false>(absolutePath);
}
template <bool ATTACH_TO_GLOBAL_EXTERNAL_SOURCES>
parser::Program *ImportPathManager::FindOrIntroduceProgram(std::string_view absolutePath)
{
ImportInfo importInfo {*this, absolutePath, Language::Id::ETS};
return LookupImportDataAndIntroduceProgram<ATTACH_TO_GLOBAL_EXTERNAL_SOURCES>(&importInfo);
}
parser::Program *ImportPathManager::EnsurePackageIsRegisteredByPackageFraction(parser::Program *fractionBeingParsed,
ir::ETSPackageDeclaration *packageDecl)
{
ES2PANDA_ASSERT(packageDecl->Program() == fractionBeingParsed);
auto packageNameNode = packageDecl->Name();
ArenaString packageName {packageNameNode->IsIdentifier() ? packageNameNode->AsIdentifier()->Name().Utf8()
: packageNameNode->AsTSQualifiedName()->Name().Utf8()};
return resolvedSources_.FixupPackageByFraction(fractionBeingParsed, packageName);
}
template <bool ATTACH_TO_GLOBAL_EXTERNAL_SOURCES>
parser::Program *ImportPathManager::LookupImportDataAndIntroduceProgram(ImportInfo *importInfo)
{
ES2PANDA_ASSERT(SearchResolved(GetGlobalProgram()->GetImportInfo()) != nullptr);
if (auto resolved = SearchResolved(*importInfo); resolved != nullptr) {
if constexpr (ATTACH_TO_GLOBAL_EXTERNAL_SOURCES) {
resolvedSources_.MaybeAddToExternalSources(resolved, GetGlobalProgram()->GetExternalDecls());
}
return resolved;
}
auto *program = LookupProgramCaches(*importInfo);
if (program != nullptr) {
ES2PANDA_ASSERT(program->Ast() != nullptr);
} else {
LookupMemCache(importInfo);
if (importInfo->Data().Kind() == ModuleKind::UNKNOWN) {
LookupDiskData(importInfo);
}
if (importInfo->Data().Kind() == ModuleKind::UNKNOWN) {
if (importInfo->PointsToPackage()) {
importInfo->SetData<ModuleKind::PACKAGE, false>(std::string(importInfo->Key()), "");
} else {
LookupSourceFile(importInfo);
}
}
program = IntroduceProgram(*importInfo);
}
if constexpr (ATTACH_TO_GLOBAL_EXTERNAL_SOURCES) {
if (program != nullptr) {
resolvedSources_.MaybeAddToExternalSources(program, GetGlobalProgram()->GetExternalDecls());
}
}
return program;
}
void ImportPathManager::LookupMemCache(ImportInfo *importInfo)
{
if (importInfo->Kind() == ModuleKind::METADATA_DECL) {
parser::ImportCache<parser::CacheType::METADATA>::GetFromCache(importInfo);
} else {
parser::ImportCache<parser::CacheType::SOURCES>::GetFromCache(importInfo);
}
}
void ImportPathManager::LookupDiskData(ImportInfo *importInfo)
{
ES2PANDA_ASSERT(importInfo->Data().Kind() == ModuleKind::UNKNOWN);
if (!importInfo->ReferencesABC()) {
LookupEtscacheFile(importInfo);
return;
}
ES2PANDA_ASSERT(importInfo->ResolvedPathIsVirtual());
auto abcPath = importInfo->AbcPath();
if (processedAbcFiles_.count(abcPath) != 0) {
LookupEtscacheFile(importInfo);
return;
}
processedAbcFiles_.insert(abcPath);
const auto pf = panda_file::OpenPandaFile(abcPath);
if (pf == nullptr) {
DE()->LogDiagnostic(diagnostic::OPEN_FAILED, DiagnosticMessageParams {abcPath});
LookupEtscacheFile(importInfo);
return;
}
if (pf->IsMetadataUsed()) {
importInfo->SetBinFile(*pf);
return;
}
ExtractEtscacheToFile(*pf, abcPath, ArkTSConfig().CacheDir());
ES2PANDA_ASSERT(importInfo->Data().Kind() == ModuleKind::UNKNOWN);
LookupEtscacheFile(importInfo);
}
void ImportPathManager::LookupEtscacheFile(ImportInfo *importInfo)
{
if (ArkTSConfig().CacheDir().empty()) {
return;
}
auto cachefile = FormEtscacheFilePath(std::string {importInfo->ModuleName()}, ArkTSConfig().CacheDir());
ES2PANDA_ASSERT(cachefile.find(ArkTSConfig().CacheDir()) == 0);
if (!ark::os::file::File::IsRegularFile(cachefile)) {
return;
}
importInfo->SetTextFile<ModuleKind::ETSCACHE_DECL>(cachefile, DE());
}
void ImportPathManager::LookupSourceFile(ImportInfo *importInfo)
{
if (importInfo->HasSpecifiedDeclPath() && !importInfo->ReferencesABC()) {
importInfo->SetTextFile<ModuleKind::SOURCE_DECL>(std::string(importInfo->DeclPath()), DE());
} else if (Helpers::EndsWith(importInfo->ResolvedSource(), D_ETS_SUFFIX)) {
importInfo->SetTextFile<ModuleKind::SOURCE_DECL>(std::string(importInfo->ResolvedSource()), DE());
} else if (importInfo->Lang() != Language::Id::ETS) {
importInfo->SetData<ModuleKind::DECLLESS_DYNAMIC, false>(std::string(importInfo->ResolvedSource()), "");
} else {
importInfo->SetTextFile<ModuleKind::MODULE>(std::string(importInfo->ResolvedSource()), DE());
}
}
ImportPathManager::ResolvedPathRes ImportPathManager::TryResolvePath(std::string resolvedPathPrototype) const
{
auto delim = pathDelimiter_[0];
std::replace_if(
resolvedPathPrototype.begin(), resolvedPathPrototype.end(), [delim](auto c) { return c == delim; }, '/');
if (ArkTSConfig().FindInDependencies(resolvedPathPrototype) != std::nullopt) {
return {resolvedPathPrototype, true};
}
if (ArkTSConfig().Paths().find(resolvedPathPrototype) != ArkTSConfig().Paths().cend()) {
return {resolvedPathPrototype, false};
}
return {{}, false};
}
std::string ImportPathManager::DirOrDirWithIndexFile(std::string resolvedPathPrototype) const
{
const auto indexFiles = {"index.ets", "index.sts", "index.ts", "index.d.ets",
"Index.ets", "Index.sts", "Index.ts", "Index.d.ets"};
for (auto indexFile : indexFiles) {
std::string indexFilePath = resolvedPathPrototype + pathDelimiter_.at(0) + indexFile;
#if defined(PANDA_TARGET_WINDOWS)
bool fileExists = ark::os::file::File::IsRegularFileCaseSensitive(indexFilePath);
#else
bool fileExists = ark::os::file::File::IsRegularFile(indexFilePath);
#endif
if (fileExists) {
return indexFilePath;
}
}
return resolvedPathPrototype;
}
ImportPathManager::ResolvedPathRes ImportPathManager::AppendExtensionOrIndexFileIfOmitted(
std::string resolvedPathPrototype) const
{
char delim = pathDelimiter_.at(0);
std::replace_if(
resolvedPathPrototype.begin(), resolvedPathPrototype.end(),
[delim](char c) { return ((delim != c) && ((c == '\\') || (c == '/'))); }, delim);
resolvedPathPrototype = ark::os::NormalizePath(resolvedPathPrototype);
if (auto resPathInfo = TryResolvePath(resolvedPathPrototype); !resPathInfo.resolvedPath.empty()) {
return resPathInfo;
}
if (ark::os::file::File::IsRegularFile(resolvedPathPrototype)) {
return {GetRealPath(resolvedPathPrototype)};
}
if (ark::os::file::File::IsDirectory(resolvedPathPrototype)) {
return {GetRealPath(DirOrDirWithIndexFile(resolvedPathPrototype))};
}
for (const auto &extension : supportedExtensions) {
auto pathWithExtension = resolvedPathPrototype + std::string(extension);
if (ark::os::file::File::IsRegularFile(pathWithExtension)) {
return {GetRealPath(pathWithExtension)};
}
}
DE()->LogDiagnostic(diagnostic::UNSUPPORTED_PATH, util::DiagnosticMessageParams {resolvedPathPrototype}, srcPos_);
return {""};
}
static std::string_view RemoveExtensionIfKnown(std::string_view relPath)
{
for (const auto &ext : ImportPathManager::supportedExtensionsInversed) {
if (relPath.size() >= ext.size() && (relPath.substr(relPath.size() - ext.size(), ext.size()) == ext)) {
return relPath.substr(0, relPath.size() - ext.size());
}
}
return relPath;
}
static ArenaString ConcatOhmurl(std::string_view p1, std::string_view p2)
{
if (!p1.empty() && ((p1.back() == util::PATH_DELIMITER) || (p1.back() == '.'))) {
p1 = p1.substr(0, p1.size() - 1);
}
if (!p2.empty() && ((p2.front() == util::PATH_DELIMITER) || (p2.front() == '.'))) {
p2 = p2.substr(1, p2.size() - 1);
}
if (p1.empty() || p2.empty()) {
return ArenaString {p1} + ArenaString {p2};
}
return ArenaString {p1} + '/' + ArenaString {p2};
}
static ArenaString RebasePathOhmurl(std::string_view pathToRebase, std::string_view oldBase,
std::string_view ohmurlStart)
{
ES2PANDA_ASSERT(Helpers::StartsWith(pathToRebase, oldBase));
auto relativePath = pathToRebase.substr(oldBase.size());
relativePath = RemoveExtensionIfKnown(relativePath);
auto res = ConcatOhmurl(ohmurlStart, relativePath);
return res;
}
static ArenaString CheckAndRebaseOhmurl(const ImportPathManager &ipm, std::string_view path, std::string_view oldBase,
std::string_view newBase)
{
if (!Helpers::StartsWith(path, oldBase)) {
ipm.Context()->diagnosticEngine->LogDiagnostic(diagnostic::SOURCE_OUTSIDE_ETS_PATH,
util::DiagnosticMessageParams {path}, ipm.SrcPos());
ES2PANDA_UNREACHABLE();
}
return RebasePathOhmurl(path, oldBase, newBase);
}
static ArenaString CreatePackageModuleName(const ImportPathManager &ipm, std::string_view resolvedSource,
std::string_view unitPath, std::string_view unitName)
{
if (!ipm.ArkTSConfig().Package().empty() &&
Helpers::StartsWith(resolvedSource, ipm.ArkTSConfig().BaseUrl() + util::PATH_DELIMITER)) {
ArenaString mnamePrototype {};
mnamePrototype =
CheckAndRebaseOhmurl(ipm, resolvedSource, ipm.ArkTSConfig().BaseUrl(), ipm.ArkTSConfig().Package());
mnamePrototype = OhmurlToMname(mnamePrototype);
ES2PANDA_ASSERT(mnamePrototype.at(0) != '.');
return mnamePrototype;
}
return OhmurlToMname(RebasePathOhmurl(resolvedSource, unitPath, unitName));
}
static std::optional<ArenaString> DeduceModuleNameByMatchingWithArktsconfig(const ImportPathManager &importPathManager,
const ImportInfo &imd)
{
using MatcherT = std::optional<ArenaString> (*)(const ArkTsConfig &, std::string_view, const ImportPathManager &);
std::vector<MatcherT> matchers {};
auto cacheDirMatcher = [](const ArkTsConfig &cfg, std::string_view resolvedSource,
[[maybe_unused]] const ImportPathManager &ipm) -> std::optional<ArenaString> {
if (cfg.CacheDir().empty() || !Helpers::StartsWith(resolvedSource, cfg.CacheDir())) {
return std::nullopt;
}
std::optional<ArenaString> mName = OhmurlToMname(CheckAndRebaseOhmurl(ipm, resolvedSource, cfg.CacheDir(), ""));
ES2PANDA_ASSERT(mName->at(0) != '.');
return mName;
};
matchers.emplace_back(cacheDirMatcher);
auto dynamicPathMatcher = [](const ArkTsConfig &cfg, std::string_view resolvedSource,
[[maybe_unused]] const ImportPathManager &ipm) {
std::optional<ArenaString> res {};
if (cfg.FindInDependencies(resolvedSource) != std::nullopt) {
res = OhmurlToMname(resolvedSource);
}
return res;
};
matchers.emplace_back(dynamicPathMatcher);
auto pathsMatcher = [](const ArkTsConfig &cfg, std::string_view resolvedSource,
const ImportPathManager &ipm) -> std::optional<ArenaString> {
for (auto const &[unitName, unitPaths] : cfg.Paths()) {
auto it = std::find_if(unitPaths.begin(), unitPaths.end(), [resolvedSource](const auto &unitPath) {
ES2PANDA_ASSERT(!unitPath.empty());
return Helpers::StartsWith(resolvedSource, unitPath);
});
if (it != unitPaths.end()) {
return CreatePackageModuleName(ipm, resolvedSource, *it, unitName);
}
}
return std::nullopt;
};
matchers.emplace_back(pathsMatcher);
for (auto matcher : matchers) {
if (auto res = (*matcher)(importPathManager.ArkTSConfig(), imd.ResolvedSource(), importPathManager); res) {
return res;
}
}
return std::nullopt;
}
static std::optional<ArenaString> CheckSpecialModuleName(const ImportInfo &imd)
{
if (imd.ResolvedSource() == STDLIB_MAIN_PROG_NAME) {
return compiler::Signatures::STDLIB_MODULE_NAME.data();
}
if (imd.ResolvedSource() == STDLIB_IMPORTS_MAIN_PROG_NAME) {
return compiler::Signatures::STDLIB_IMPORTS_MODULE_NAME.data();
}
if (imd.ResolvedSource() == SIMULT_MAIN_PROG_NAME) {
return compiler::Signatures::SIMULT_MODULE_NAME.data();
}
return std::nullopt;
}
static ArenaString FormModuleNameSolelyByAbsolutePath(const ImportPathManager &ipm, const ImportInfo &imd)
{
auto etsPath = ipm.Context()->config->options->GetEtsPath();
auto absoluteEtsPath = util::Path(etsPath, ipm.Context()->allocator).GetAbsolutePath().Utf8();
return OhmurlToMname(CheckAndRebaseOhmurl(ipm, imd.ResolvedSource(), absoluteEtsPath, ""));
}
static ArenaString FormModuleName(const ImportPathManager &ipm, const ImportInfo &imd)
{
ES2PANDA_ASSERT(imd.ModuleName().empty());
if (auto res = CheckSpecialModuleName(imd); res) {
return *res;
}
if (ipm.Context()->config->options->WasSetEtsPath()) {
return FormModuleNameSolelyByAbsolutePath(ipm, imd);
}
if (auto res = DeduceModuleNameByMatchingWithArktsconfig(ipm, imd); res) {
return *res;
}
if (Helpers::StartsWith(imd.ResolvedSource(), ipm.ArkTSConfig().BaseUrl())) {
ArenaString mnamePrototype {};
if (!ipm.ArkTSConfig().Package().empty()) {
mnamePrototype += ipm.ArkTSConfig().Package();
}
if (ipm.ArkTSConfig().UseUrl() || !ipm.ArkTSConfig().Package().empty()) {
auto rebased = RebasePathOhmurl(imd.ResolvedSource(), ipm.ArkTSConfig().BaseUrl(), "");
if (!mnamePrototype.empty() && !rebased.empty() && (rebased[0] != '/')) {
mnamePrototype += '.';
}
mnamePrototype += OhmurlToMname(rebased);
ES2PANDA_ASSERT(mnamePrototype.at(0) != '.');
return mnamePrototype;
}
}
return ArenaString(util::Path(imd.ResolvedSource(), ipm.Context()->allocator).GetFileName().Utf8());
}
static void CheckNoColonInName(std::string_view name, util::DiagnosticEngine *diagnosticEngine)
{
if (name.find(':') != std::string_view::npos) {
util::DiagnosticMessageParams diagParams = {name};
diagnosticEngine->LogDiagnostic(diagnostic::UNSUPPORTED_FILE_NAME, diagParams);
}
}
static void CheckModuleName(const ImportPathManager &ipm, const ImportInfo &imd)
{
CheckNoColonInName(imd.ModuleName(), ipm.Context()->diagnosticEngine);
if (imd.ModuleName().empty()) {
ipm.Context()->diagnosticEngine->LogDiagnostic(diagnostic::UNRESOLVED_MODULE,
DiagnosticMessageParams {imd.TextSource()});
}
}
ImportInfo::ImportInfo(const ImportPathManager &ipm, std::string_view resolvedSource, Language::Id lang,
bool isExternalModule)
: resolvedSource_ {resolvedSource}
{
SetKey(resolvedSource_);
if (isExternalModule) {
ES2PANDA_ASSERT(ResolvedPathIsVirtual());
auto dependenciesLookupResult = ipm.ArkTSConfig().FindInDependencies(ResolvedSource());
ES2PANDA_ASSERT(dependenciesLookupResult != std::nullopt);
extModuleData_ = &dependenciesLookupResult->second;
lang_ = extModuleData_->GetLanguage().GetId();
} else {
lang_ = lang;
}
moduleName_ = FormModuleName(ipm, *this);
CheckModuleName(ipm, *this);
}
ImportInfo::ImportInfo(const ImportInfo &other)
{
*this = other;
}
const ImportInfo &ImportInfo::operator=(const ImportInfo &other)
{
parser::CacheReference<>::operator=(other);
resolvedSource_ = other.resolvedSource_;
moduleName_ = other.moduleName_;
extModuleData_ = other.extModuleData_;
lang_ = other.lang_;
SetKey(resolvedSource_);
return *this;
}
std::string_view ImportInfo::DeclPath() const
{
return (extModuleData_ != nullptr) ? extModuleData_->Path() : std::string_view {};
}
std::string_view ImportInfo::OhmUrl() const
{
if ((extModuleData_ != nullptr) && !extModuleData_->OhmUrl().empty()) {
return extModuleData_->OhmUrl();
}
if (ReferencesABC()) {
return AbcPath();
}
return "";
}
bool ImportInfo::HasSpecifiedDeclPath() const
{
return !DeclPath().empty() && (DeclPath() != DUMMY_PATH);
}
bool ImportInfo::IsValid() const
{
return !resolvedSource_.empty() && (resolvedSource_ != ERROR_LITERAL);
}
ImportPathManager::ImportPathManager(public_lib::Context *context)
: ctx_(*context),
parseQueue_(context->Allocator()->Adapter()),
resolvedSources_(*ArenaAllocator::New<ResolvedSources>(this))
{
}
}
#undef USE_UNIX_SYSCALL