| // 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; |
| } |