/* Copyright (c) 2017 Microsoft Corporation. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Author: Leonardo de Moura, Gabriel Ebner */ #include "util/lean_path.h" #include #include #include #include #include "runtime/sstream.h" #ifndef LEAN_DEFAULT_MODULE_FILE_NAME #define LEAN_DEFAULT_MODULE_FILE_NAME "default" #endif namespace lean { lean_file_not_found_exception::lean_file_not_found_exception(std::string const & fname): exception(sstream() << "file '" << fname << "' not found in the LEAN_PATH"), m_fname(fname) {} static char const * g_default_file_name = LEAN_DEFAULT_MODULE_FILE_NAME; static bool exists(std::string const & fn) { return !!std::ifstream(fn); } optional get_leanpkg_path_file() { auto dir = lrealpath("."); while (true) { auto fn = dir + get_dir_sep() + "leanpkg.path"; if (exists(fn)) return optional(fn); auto i = dir.rfind(get_dir_sep()); if (i == std::string::npos) { return optional(); } else { dir = dir.substr(0, i); } } } std::string get_user_leanpkg_path() { // TODO(gabriel): check if this works on windows if (auto home = getenv("HOME")) { return std::string(home) + get_dir_sep() + ".lean" + get_dir_sep() + "leanpkg.path"; } else { return "/could-not-find-home"; } } static optional begins_with(std::string const & s, std::string const & prefix) { if (prefix.size() <= s.size() && s.substr(0, prefix.size()) == prefix) { return optional(s.substr(prefix.size(), s.size())); } else { return optional(); } } search_path parse_leanpkg_path(std::string const & fn) { std::ifstream in(fn); if (!in) throw exception(sstream() << "cannot open " << fn); auto fn_dir = dirname(fn); search_path path; while (!in.eof()) { std::string line; std::getline(in, line); if (auto rest = begins_with(line, "path ")) path.push_back(lrealpath(resolve(*rest, fn_dir))); if (line == "builtin_path") { auto builtin = get_builtin_search_path(); path.insert(path.end(), builtin.begin(), builtin.end()); } } return path; } optional get_lean_path_from_env() { if (auto r = getenv("LEAN_PATH")) { auto lean_path = normalize_path(r); unsigned i = 0; unsigned j = 0; unsigned sz = static_cast(lean_path.size()); search_path path; for (; j < sz; j++) { if (is_path_sep(lean_path[j])) { if (j > i) path.push_back(lrealpath(lean_path.substr(i, j - i))); i = j + 1; } } if (j > i) path.push_back(lrealpath(lean_path.substr(i, j - i))); return optional(path); } else { return optional(); } } search_path get_builtin_search_path() { search_path path; #if !defined(LEAN_EMSCRIPTEN) std::string exe_path = dirname(get_exe_location()); auto lib_path = exe_path + get_dir_sep() + ".." + get_dir_sep() + "library"; if (exists(lib_path)) path.push_back(lrealpath(lib_path)); auto installed_lib_path = exe_path + get_dir_sep() + ".." + get_dir_sep() + "lib" + get_dir_sep() + "lean" + get_dir_sep() + "library"; if (exists(installed_lib_path)) path.push_back(lrealpath(installed_lib_path)); #endif return path; } standard_search_path::standard_search_path() { m_builtin = get_builtin_search_path(); m_from_env = get_lean_path_from_env(); m_leanpkg_path_fn = get_leanpkg_path_file(); m_user_leanpkg_path_fn = get_user_leanpkg_path(); if (m_leanpkg_path_fn) { m_from_leanpkg_path = parse_leanpkg_path(*m_leanpkg_path_fn); } else if (exists(m_user_leanpkg_path_fn)) { m_from_leanpkg_path = parse_leanpkg_path(m_user_leanpkg_path_fn); } } search_path standard_search_path::get_path() const { if (m_from_env) return *m_from_env; if (m_from_leanpkg_path) return *m_from_leanpkg_path; return m_builtin; } bool is_lean_file(std::string const & fname) { return has_file_ext(fname, ".lean"); } bool is_olean_file(std::string const & fname) { return has_file_ext(fname, ".olean"); } bool is_known_file_ext(std::string const & fname) { return is_lean_file(fname) || is_olean_file(fname); } optional check_file_core(std::string file, char const * ext) { if (ext) file += ext; std::ifstream ifile(file); if (ifile) return optional(lrealpath(file)); else return optional(); } optional check_file(std::string const & path, std::string const & fname, char const * ext = nullptr) { std::string file = path + get_dir_sep() + fname; if (is_directory(file.c_str())) { std::string default_file = file + get_dir_sep() + g_default_file_name; if (auto r1 = check_file_core(default_file, ext)) { if (auto r2 = check_file_core(file, ext)) throw exception(sstream() << "ambiguous import, it can be '" << *r1 << "' or '" << *r2 << "'"); return r1; } } return check_file_core(file, ext); } std::string name_to_file(name const & fname) { return fname.to_string(get_dir_sep()); } static std::string find_file(search_path const & paths, std::string fname, std::initializer_list const & extensions) { bool is_known = is_known_file_ext(fname); fname = normalize_path(fname); buffer results; for (auto & path : paths) { if (is_known) { if (auto r = check_file(path, fname)) results.push_back(*r); } else { for (auto ext : extensions) { if (auto r = check_file(path, fname, ext)) results.push_back(*r); } } } if (results.size() == 0) throw lean_file_not_found_exception(fname); else if (results.size() > 1) throw exception(sstream() << "ambiguous import, it can be '" << results[0] << "' or '" << results[1] << "'"); else return results[0]; } std::string find_file(search_path const & paths, std::string const & base, optional const & rel, name const & fname, std::initializer_list const & extensions) { if (!rel) { return find_file(paths, fname.to_string(get_dir_sep()), extensions); } else { auto path = base; for (unsigned i = 0; i < *rel; i++) { path += get_dir_sep(); path += ".."; } for (auto ext : extensions) { if (auto r = check_file(path, fname.to_string(get_dir_sep()), ext)) return *r; } throw lean_file_not_found_exception(fname.to_string()); } } std::string find_file(search_path const & paths, std::string const & base, optional const & k, name const & fname, char const * ext) { return find_file(paths, base, k, fname, {ext}); } std::string find_file(search_path const & paths, std::string fname) { return find_file(paths, fname, {".olean", ".lean"}); } std::string find_file(search_path const & paths, name const & fname) { return find_file(paths, fname.to_string(get_dir_sep())); } std::string find_file(search_path const & paths, name const & fname, std::initializer_list const & exts) { return find_file(paths, fname.to_string(get_dir_sep()), exts); } name module_name_of_file(search_path const & paths, std::string const & fname0) { auto fname = normalize_path(fname0); lean_assert(is_lean_file(fname)); fname = fname.substr(0, fname.size() - std::string(".lean").size()); for (auto & path : paths) { if (path.size() < fname.size() && fname.substr(0, path.size()) == path) { size_t pos = path.size(); if (fname[pos] == get_dir_sep_ch()) pos++; name n; while (pos < fname.size()) { auto sep_pos = fname.find(get_dir_sep_ch(), pos); n = name(n, fname.substr(pos, sep_pos - pos).c_str()); pos = sep_pos; if (pos != std::string::npos) pos++; } return n; } } throw exception(sstream() << "file '" << fname0 << "' not part of any known Lean packages"); } module_name absolutize_module_name(search_path const & path, std::string const & base, rel_module_name const & rel) { // TODO(Sebastian): Should make sure that the result of `find_file` is still in the same package as `base` return module_name_of_file(path, find_file(path, base, rel.m_updirs, rel.m_name, ".lean")); } void find_imports_core(std::string const & base, optional const & k, std::vector> & imports) { std::vector files; find_files(base, ".lean", files); find_files(base, ".olean", files); for (auto const & file : files) { auto import = file.substr(base.size() + 1, file.rfind('.') - (base.size() + 1)); std::replace(import.begin(), import.end(), get_dir_sep_ch(), '.'); if (k) import = std::string(*k + 1, '.') + import; auto n = import.rfind(".default"); if (n != static_cast(-1) && n == import.size() - std::string(".default").size()) import = import.substr(0, n); imports.push_back({import, file}); } } void find_imports(search_path const & paths, std::string const & base, optional const & k, std::vector> & imports) { if (!k) { for (auto & base : paths) if (is_dir(base)) find_imports_core(base, k, imports); } else { auto path = base; for (unsigned i = 0; i < *k; i++) { path += get_dir_sep(); path += ".."; } find_imports_core(path, k, imports); } } std::string olean_of_lean(std::string const & lean_fn) { if (lean_fn.size() > 5 && lean_fn.substr(lean_fn.size() - 5) == ".lean") { return lean_fn.substr(0, lean_fn.size() - 5) + ".olean"; } else { throw exception(sstream() << "not a .lean file: " << lean_fn); } } std::string olean_file_to_lean_file(std::string const &olean) { lean_assert(is_olean_file(olean)); std::string lean = olean; lean.erase(lean.size() - std::string("olean").size(), 1); return lean; } }