diff --git a/.travis.yml b/.travis.yml index ede4b132b..d94c030cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,6 +51,7 @@ script: - cd .. - cp travis/dfhack_travis.init "$DF_FOLDER"/ - python travis/run-tests.py "$DF_FOLDER" +- python travis/check-rpc.py "$DF_FOLDER/dfhack-rpc.txt" notifications: email: false irc: diff --git a/library/Core.cpp b/library/Core.cpp index 463175e0f..2d046b0f7 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -54,6 +54,7 @@ using namespace std; #include "modules/Graphic.h" #include "modules/Windows.h" #include "RemoteServer.h" +#include "RemoteTools.h" #include "LuaTools.h" #include "DFHackVersion.h" @@ -632,6 +633,9 @@ string getBuiltinCommand(std::string cmd) else if (cmd == "clear") builtin = "cls"; + else if (cmd == "devel/dump-rpc") + builtin = "devel/dump-rpc"; + return builtin; } @@ -1278,6 +1282,34 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v return CR_WRONG_USAGE; } } + else if (builtin == "devel/dump-rpc") + { + if (parts.size() == 1) + { + std::ofstream file(parts[0]); + CoreService core; + core.dumpMethods(file); + + for (auto & it : *plug_mgr) + { + Plugin * plug = it.second; + if (!plug) + continue; + + std::unique_ptr svc(plug->rpc_connect(con)); + if (!svc) + continue; + + file << "// Plugin: " << plug->getName() << endl; + svc->dumpMethods(file); + } + } + else + { + con << "Usage: devel/dump-rpc \"filename\"" << endl; + return CR_WRONG_USAGE; + } + } else if (RunAlias(con, first, parts, res)) { return res; diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index f110cc8b4..86ef9f40c 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -109,6 +109,24 @@ void RPCService::finalize(ServerConnection *owner, std::vectorp_in_template->GetTypeName(); + size_t last_dot = in_name.rfind('.'); + if (last_dot != std::string::npos) + in_name = in_name.substr(last_dot + 1); + + std::string out_name = fn->p_out_template->GetTypeName(); + last_dot = out_name.rfind('.'); + if (last_dot != std::string::npos) + out_name = out_name.substr(last_dot + 1); + + out << "// RPC " << fn->name << " : " << in_name << " -> " << out_name << endl; + } +} + ServerConnection::ServerConnection(CActiveSocket *socket) : socket(socket), stream(this) { diff --git a/library/include/RemoteServer.h b/library/include/RemoteServer.h index 514f91250..85ac463fb 100644 --- a/library/include/RemoteServer.h +++ b/library/include/RemoteServer.h @@ -151,6 +151,7 @@ namespace DFHack class DFHACK_EXPORT RPCService { friend class ServerConnection; friend class Plugin; + friend class Core; std::vector functions; std::map lookup; @@ -208,6 +209,8 @@ namespace DFHack assert(!owner); functions.push_back(new VoidServerMethod(this, name, flags, fptr)); } + + void dumpMethods(std::ostream & out) const; }; class ServerConnection { diff --git a/travis/check-rpc.py b/travis/check-rpc.py new file mode 100644 index 000000000..7b771ee67 --- /dev/null +++ b/travis/check-rpc.py @@ -0,0 +1,109 @@ +import glob +import sys + +actual = {'': {}} + +with open(sys.argv[1]) as f: + plugin_name = '' + for line in f: + line = line.rstrip() + if line.startswith('// Plugin: '): + plugin_name = line.split(' ')[2] + if plugin_name not in actual: + actual[plugin_name] = {} + elif line.startswith('// RPC '): + parts = line.split(' ') + actual[plugin_name][parts[2]] = (parts[4], parts[6]) + +expected = {'': {}} + +for p in glob.iglob('library/proto/*.proto'): + with open(p) as f: + for line in f: + line = line.rstrip() + if line.startswith('// RPC '): + parts = line.split(' ') + expected[''][parts[2]] = (parts[4], parts[6]) + +for p in glob.iglob('plugins/proto/*.proto'): + plugin_name = '' + with open(p) as f: + for line in f: + line = line.rstrip() + if line.startswith('// Plugin: '): + plugin_name = line.split(' ')[2] + if plugin_name not in expected: + expected[plugin_name] = {} + break + + if plugin_name == '': + continue + + with open(p) as f: + for line in f: + line = line.rstrip() + if line.startswith('// RPC '): + parts = line.split(' ') + expected[plugin_name][parts[2]] = (parts[4], parts[6]) + +error_count = 0 + +for plugin_name in actual: + methods = actual[plugin_name] + + if plugin_name not in expected: + print('Missing documentation for plugin proto files: ' + plugin_name) + print('Add the following lines:') + print('// Plugin: ' + plugin_name) + error_count += 1 + for m in methods: + io = methods[m] + print('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) + error_count += 1 + else: + missing = [] + wrong = [] + for m in methods: + io = methods[m] + if m in expected[plugin_name]: + if expected[plugin_name][m] != io: + wrong.add('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) + else: + missing.add('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) + + if len(missing) > 0: + print('Incomplete documentation for ' + ('core' if plugin_name == '' else 'plugin "' + plugin_name + '"') + ' proto files. Add the following lines:') + for m in missing: + print(m) + error_count += 1 + + if len(wrong) > 0: + print('Incorrect documentation for ' + ('core' if plugin_name == '' else 'plugin "' + plugin_name + '"') + ' proto files. Replace the following comments:') + for m in wrong: + print(m) + error_count += 1 + +for plugin_name in expected: + methods = expected[plugin_name] + + if plugin_name not in actual: + print('Incorrect documentation for plugin proto files: ' + plugin_name) + print('The following methods are documented, but the plugin does not provide any RPC methods:') + for m in methods: + io = methods[m] + print('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) + error_count += 1 + else: + missing = [] + for m in methods: + io = methods[m] + if m not in actual[plugin_name]: + missing.add('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) + + if len(missing) > 0: + print('Incorrect documentation for ' + ('core' if plugin_name == '' else 'plugin "' + plugin_name + '"') + ' proto files. Remove the following lines:') + for m in missing: + print(m) + error_count += 1 + +sys.exit(min(100, error_count)) diff --git a/travis/dfhack_travis.init b/travis/dfhack_travis.init index d9bc3e5ba..eb7858bbe 100644 --- a/travis/dfhack_travis.init +++ b/travis/dfhack_travis.init @@ -1,2 +1,4 @@ +devel/dump-rpc dfhack-rpc.txt + :lua dfhack.internal.addScriptPath(os.getenv('TRAVIS_BUILD_DIR')) test/main