| // Copyright 2017-2020 The Verible Authors. | |
| // | |
| // 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 <functional> | |
| #include <iostream> | |
| #include <memory> | |
| #include <string> | |
| #include "absl/flags/flag.h" | |
| #include "absl/flags/usage.h" | |
| #include "absl/status/status.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/string_view.h" | |
| #include "common/util/file_util.h" | |
| #include "common/util/init_command_line.h" | |
| #include "common/util/logging.h" | |
| #include "common/util/subcommand.h" | |
| #include "verilog/analysis/dependencies.h" | |
| #include "verilog/analysis/symbol_table.h" | |
| #include "verilog/analysis/verilog_project.h" | |
| // Note: These flags were copied over from | |
| // verilog/tools/kythe/verilog_kythe_extractor.cc. | |
| // TODO: standardize Verilog project flags across tools. | |
| ABSL_FLAG( | |
| std::string, file_list_path, "", | |
| R"(The path to the file list which contains the names of SystemVerilog files. | |
| The files should be ordered by definition dependencies.)"); | |
| ABSL_FLAG( | |
| std::string, file_list_root, ".", | |
| R"(The absolute location which we prepend to the files in the file list (where listed files are relative to).)"); | |
| // TODO: support repeatable flag | |
| ABSL_FLAG( | |
| std::vector<std::string>, include_dir_paths, {}, | |
| R"(Comma separated paths of the directories used to look for included files. | |
| Note: The order of the files here is important. | |
| File search will stop at the the first found among the listed directories. | |
| e.g --include_dir_paths directory1,directory2 | |
| if "A.sv" exists in both "directory1" and "directory2" the one in "directory1" is the one we will use. | |
| )"); | |
| using verible::SubcommandArgsRange; | |
| using verible::SubcommandEntry; | |
| // Project configuration information expected to come from command-line | |
| // invocation. | |
| // TODO: refactor for re-use in verilog/tools/kythe/verilog_kythe_extractor.cc | |
| struct VerilogProjectConfig { | |
| verilog::FileList file_list; | |
| // See --file_list_root above. | |
| std::string file_list_root; | |
| absl::Status LoadFromGlobalFlags() { | |
| file_list.preprocessing.include_dirs = | |
| absl::GetFlag(FLAGS_include_dir_paths); | |
| file_list.file_list_path = absl::GetFlag(FLAGS_file_list_path); | |
| if (file_list.file_list_path.empty()) { | |
| return absl::InvalidArgumentError( | |
| "--file_list_path is required but missing."); | |
| } | |
| file_list_root = absl::GetFlag(FLAGS_file_list_root); | |
| return verilog::AppendFileListFromFile(file_list.file_list_path, | |
| &file_list); | |
| } | |
| }; | |
| // Holds VerilogProject and SymbolTable together with proper object lifetime. | |
| // TODO: refactor this for re-use with tools/kythe/verilog_kythe_extractor.cc. | |
| struct ProjectSymbols { | |
| // From global absl flags. | |
| const VerilogProjectConfig& config; | |
| // This object must outlive 'symbol_table' (maintained by struct-ordering). | |
| std::unique_ptr<verilog::VerilogProject> project; | |
| // Unified symbol table. | |
| std::unique_ptr<verilog::SymbolTable> symbol_table; | |
| explicit ProjectSymbols(const VerilogProjectConfig& config) | |
| : config(config) {} | |
| // no copy, no move, no assign | |
| ProjectSymbols(const ProjectSymbols&) = delete; | |
| ProjectSymbols(ProjectSymbols&&) = delete; | |
| ProjectSymbols& operator=(const ProjectSymbols&) = delete; | |
| ProjectSymbols& operator=(ProjectSymbols&&) = delete; | |
| // Initializes a project, and opens listed files. | |
| absl::Status Load() { | |
| VLOG(1) << __FUNCTION__; | |
| // Load all source files first. | |
| // Error-out early if any files failed to open. | |
| project = std::make_unique<verilog::VerilogProject>( | |
| config.file_list_root, config.file_list.preprocessing.include_dirs); | |
| for (const auto& file : config.file_list.file_paths) { | |
| const auto open_status = project->OpenTranslationUnit(file); | |
| if (!open_status.ok()) return open_status.status(); | |
| } | |
| // Initialize symbol table (empty). | |
| symbol_table = std::make_unique<verilog::SymbolTable>(project.get()); | |
| return absl::OkStatus(); | |
| } | |
| // Builds symbol table. | |
| void Build(std::vector<absl::Status>* build_statuses) { | |
| VLOG(1) << __FUNCTION__; | |
| // For now, ingest files in the order they were listed. | |
| // Without conflicting definitions in files, this order should not matter. | |
| for (const auto& file : config.file_list.file_paths) { | |
| symbol_table->BuildSingleTranslationUnit(file, build_statuses); | |
| } | |
| } | |
| // Resolves symbols. | |
| void Resolve(std::vector<absl::Status>* resolve_statuses) const { | |
| symbol_table->Resolve(resolve_statuses); | |
| } | |
| }; | |
| static std::string JoinStatusMessages( | |
| const std::vector<absl::Status>& statuses) { | |
| return absl::StrCat( | |
| "[combined statuses]:\n", | |
| absl::StrJoin( | |
| statuses, "\n", [](std::string* out, const absl::Status& status) { | |
| out->append(status.message().begin(), status.message().end()); | |
| })); | |
| } | |
| static absl::Status BuildAndShowSymbolTable(const SubcommandArgsRange& args, | |
| std::istream& ins, | |
| std::ostream& outs, | |
| std::ostream& errs) { | |
| VLOG(1) << __FUNCTION__; | |
| // Load configuration. | |
| VerilogProjectConfig config; | |
| if (auto status = config.LoadFromGlobalFlags(); !status.ok()) { | |
| return status; | |
| } | |
| // Load project and files. | |
| ProjectSymbols project_symbols(config); | |
| if (auto status = project_symbols.Load(); !status.ok()) { | |
| return status; | |
| } | |
| // Build symbol table. | |
| std::vector<absl::Status> build_statuses; | |
| project_symbols.Build(&build_statuses); | |
| // Print. | |
| outs << "Symbol Table:" << std::endl; | |
| project_symbols.symbol_table->PrintSymbolDefinitions(outs) << std::endl; | |
| // Accumulate diagnostics. | |
| if (!build_statuses.empty()) { | |
| return absl::InvalidArgumentError(JoinStatusMessages(build_statuses)); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| static absl::Status ResolveAndShowSymbolReferences( | |
| const SubcommandArgsRange& args, std::istream& ins, std::ostream& outs, | |
| std::ostream& errs) { | |
| VLOG(1) << __FUNCTION__; | |
| // Load configuration. | |
| VerilogProjectConfig config; | |
| if (auto status = config.LoadFromGlobalFlags(); !status.ok()) { | |
| return status; | |
| } | |
| // Load project and files. | |
| ProjectSymbols project_symbols(config); | |
| if (auto status = project_symbols.Load(); !status.ok()) { | |
| return status; | |
| } | |
| // Build symbol table. | |
| std::vector<absl::Status> statuses; | |
| project_symbols.Build(&statuses); | |
| // Resolve symbols. | |
| project_symbols.Resolve(&statuses); | |
| // Print. | |
| outs << "Symbol References:" << std::endl; | |
| project_symbols.symbol_table->PrintSymbolReferences(outs) << std::endl; | |
| // Accumulate diagnostics. | |
| if (!statuses.empty()) { | |
| return absl::InvalidArgumentError(JoinStatusMessages(statuses)); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| static absl::Status ShowFileDependencies(const SubcommandArgsRange& args, | |
| std::istream& ins, std::ostream& outs, | |
| std::ostream& errs) { | |
| VLOG(1) << __FUNCTION__; | |
| // Load configuration. | |
| VerilogProjectConfig config; | |
| if (auto status = config.LoadFromGlobalFlags(); !status.ok()) { | |
| return status; | |
| } | |
| // Load project and files. | |
| ProjectSymbols project_symbols(config); | |
| if (auto status = project_symbols.Load(); !status.ok()) { | |
| return status; | |
| } | |
| // Build symbol table. | |
| std::vector<absl::Status> statuses; | |
| project_symbols.Build(&statuses); | |
| // Accumulate diagnostics. | |
| if (!statuses.empty()) { | |
| return absl::InvalidArgumentError(JoinStatusMessages(statuses)); | |
| } | |
| // Partially resolve symbols. | |
| project_symbols.symbol_table->ResolveLocallyOnly(); | |
| // Compute dependencies. | |
| const verilog::FileDependencies deps(*project_symbols.symbol_table); | |
| // Print. | |
| // TODO(hzeller): support various output options {human-readable, | |
| // machine-readable, etc.} using subcommand flags (b/164300992). | |
| // One variant should include tsort-consumable 2-column text. | |
| deps.PrintGraph(outs); | |
| return absl::OkStatus(); | |
| } | |
| static const std::pair<absl::string_view, SubcommandEntry> kCommands[] = { | |
| {"symbol-table-defs", // | |
| {&BuildAndShowSymbolTable, // | |
| R"(symbol-table-defs [project args] | |
| Prints human-readable unified symbol table representation of all files. | |
| This does not attempt to resolve symbols. | |
| Input: | |
| Project options, including source file list. | |
| )"}}, | |
| {"symbol-table-refs", // | |
| {&ResolveAndShowSymbolReferences, // | |
| R"(symbol-table-refs [project args] | |
| Prints human-readable representation of symbol table references, after | |
| attempting to resolve symbols. | |
| Input: | |
| Project options, including source file list. | |
| )"}}, | |
| {"file-deps", // | |
| {&ShowFileDependencies, // | |
| R"(file-deps [project args] | |
| Prints human-readable representation of inter-file dependencies, e.g. | |
| "file1.sv" depends on "file2.sv" for symbols { X, Y, Z... } | |
| Input: | |
| Project options, including source file list. | |
| )"}}, | |
| // TODO: project-wide transformations like RenameSymbol() | |
| // TODO: symbol table name-completion demo | |
| }; | |
| int main(int argc, char* argv[]) { | |
| // Create a registry of subcommands (locally, rather than as a static global). | |
| verible::SubcommandRegistry commands; | |
| for (const auto& entry : kCommands) { | |
| const auto status = commands.RegisterCommand(entry.first, entry.second); | |
| if (!status.ok()) { | |
| std::cerr << status.message() << std::endl; | |
| return 2; // fatal error | |
| } | |
| } | |
| const std::string usage = absl::StrCat("usage: ", argv[0], | |
| " command args...\n" | |
| "available commands:\n", | |
| commands.ListCommands()); | |
| // Process invocation args. | |
| const auto args = verible::InitCommandLine(usage, &argc, &argv); | |
| if (args.size() == 1) { | |
| std::cerr << absl::ProgramUsageMessage() << std::endl; | |
| return 1; | |
| } | |
| // args[0] is the program name | |
| // args[1] is the subcommand | |
| // subcommand args start at [2] | |
| const SubcommandArgsRange command_args(args.cbegin() + 2, args.cend()); | |
| const auto& sub = commands.GetSubcommandEntry(args[1]); | |
| // Run the subcommand. | |
| const auto status = sub.main(command_args, std::cin, std::cout, std::cerr); | |
| if (!status.ok()) { | |
| std::cerr << status.message() << std::endl; | |
| return 1; | |
| } | |
| return 0; | |
| } |