api: api_info()['version']

API level is disconnected from NVIM version. The API metadata holds the
current API level, and the lowest backwards-compatible level supported
by this instance.

Release 0.1.6 is the first release that reports the Nvim version and API
level.

    metadata['version'] = {
      major: 0,
      minor: 1,
      patch: 6,
      api_level: 1,
      api_compatible: 0,
      api_prerelease: false,
    }

The API level may remain unchanged across Nvim releases if the API has
not changed.

When changing the API,
    - set NVIM_API_PRERELEASE to true
    - increment NVIM_API_LEVEL (at most once per Nvim version)
    - adjust NVIM_API_LEVEL_COMPAT if backwards-compatibility was broken

api_level_0.mpack was generated from Nvim 0.1.5 with:
    nvim --api-info
This commit is contained in:
Justin M. Keyes
2016-10-26 15:20:00 +02:00
parent f25797f869
commit c5f5f427c6
11 changed files with 100 additions and 95 deletions

View File

@@ -59,16 +59,15 @@ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY
STRINGS "Debug" "Dev" "Release" "MinSizeRel" "RelWithDebInfo") STRINGS "Debug" "Dev" "Release" "MinSizeRel" "RelWithDebInfo")
# If not in a git repo (e.g., a tarball) these tokens define the complete # If not in a git repo (e.g., a tarball) these tokens define the complete
# version string, else it is combined with the result of `git describe`. # version string, else they are combined with the result of `git describe`.
set(NVIM_VERSION_MAJOR 0) set(NVIM_VERSION_MAJOR 0)
set(NVIM_VERSION_MINOR 1) set(NVIM_VERSION_MINOR 1)
set(NVIM_VERSION_PATCH 6) set(NVIM_VERSION_PATCH 6)
set(NVIM_VERSION_PRERELEASE "-dev") # for package maintainers set(NVIM_VERSION_PRERELEASE "-dev") # for package maintainers
# Neovim API version. When changing the API, bump CURRENT if # API level
# PRERELEASE is false, and set PRERELEASE as true set(NVIM_API_LEVEL 1) # Bump this after any API change.
set(NVIM_API_CURRENT 1) set(NVIM_API_LEVEL_COMPAT 0) # Adjust this after a _breaking_ API change.
set(NVIM_API_COMPATIBILITY 0)
set(NVIM_API_PRERELEASE true) set(NVIM_API_PRERELEASE true)
file(TO_CMAKE_PATH ${CMAKE_CURRENT_LIST_DIR}/.git FORCED_GIT_DIR) file(TO_CMAKE_PATH ${CMAKE_CURRENT_LIST_DIR}/.git FORCED_GIT_DIR)

View File

@@ -7,9 +7,9 @@
#define NVIM_VERSION_PRERELEASE "@NVIM_VERSION_PRERELEASE@" #define NVIM_VERSION_PRERELEASE "@NVIM_VERSION_PRERELEASE@"
#cmakedefine NVIM_VERSION_MEDIUM "@NVIM_VERSION_MEDIUM@" #cmakedefine NVIM_VERSION_MEDIUM "@NVIM_VERSION_MEDIUM@"
#define NVIM_API_CURRENT @NVIM_API_CURRENT@ #define NVIM_API_LEVEL @NVIM_API_LEVEL@
#define NVIM_API_COMPATIBILITY @NVIM_API_COMPATIBILITY@ #define NVIM_API_LEVEL_COMPAT @NVIM_API_LEVEL_COMPAT@
#cmakedefine NVIM_API_PRERELEASE #define NVIM_API_PRERELEASE @NVIM_API_PRERELEASE@
#define NVIM_VERSION_CFLAGS "@NVIM_VERSION_CFLAGS@" #define NVIM_VERSION_CFLAGS "@NVIM_VERSION_CFLAGS@"
#define NVIM_VERSION_BUILD_TYPE "@NVIM_VERSION_BUILD_TYPE@" #define NVIM_VERSION_BUILD_TYPE "@NVIM_VERSION_BUILD_TYPE@"

View File

@@ -51,10 +51,10 @@ Tabpage -> enum value kObjectTypeTabpage
Nvim exposes metadata about the API as a Dictionary with the following keys: Nvim exposes metadata about the API as a Dictionary with the following keys:
api_level API version compatibility information version Nvim version, API level/compatibility
functions calling signature of the API functions functions API function signatures
types The custom handle types defined by Nvim types Custom handle types defined by Nvim
error_types The possible kinds of errors an API function can exit with. error_types Possible error types returned by API functions
This metadata is mostly useful for external programs accessing the API via This metadata is mostly useful for external programs accessing the API via
RPC, see |rpc-api|. RPC, see |rpc-api|.

View File

@@ -168,9 +168,8 @@ API metadata object ~
API clients exist to hide msgpack-rpc details. The API metadata object API clients exist to hide msgpack-rpc details. The API metadata object
contains information that makes this task easier (see also |rpc-types|): contains information that makes this task easier (see also |rpc-types|):
- The "api_level" key contais API compatibility information. The "current" - The "version" key contains the Nvim version, API level, and API
key holds the API version supported Neovim. The "compatibility" key holds backwards-compatibility level.
the oldest supported API version.
- The "functions" key contains a list of metadata objects for individual - The "functions" key contains a list of metadata objects for individual
functions. functions.
- Each function metadata object has |rpc-types| information about the return - Each function metadata object has |rpc-types| information about the return

View File

@@ -18,9 +18,9 @@
#include "nvim/map.h" #include "nvim/map.h"
#include "nvim/option.h" #include "nvim/option.h"
#include "nvim/option_defs.h" #include "nvim/option_defs.h"
#include "nvim/version.h"
#include "nvim/eval/typval_encode.h" #include "nvim/eval/typval_encode.h"
#include "nvim/lib/kvec.h" #include "nvim/lib/kvec.h"
#include "auto/versiondef.h"
/// Helper structure for vim_to_object /// Helper structure for vim_to_object
typedef struct { typedef struct {
@@ -764,10 +764,10 @@ Dictionary api_metadata(void)
static Dictionary metadata = ARRAY_DICT_INIT; static Dictionary metadata = ARRAY_DICT_INIT;
if (!metadata.size) { if (!metadata.size) {
PUT(metadata, "version", DICTIONARY_OBJ(version_dict()));
init_function_metadata(&metadata); init_function_metadata(&metadata);
init_error_type_metadata(&metadata); init_error_type_metadata(&metadata);
init_type_metadata(&metadata); init_type_metadata(&metadata);
init_api_level_metadata(&metadata);
} }
return copy_object(DICTIONARY_OBJ(metadata)).data.dictionary; return copy_object(DICTIONARY_OBJ(metadata)).data.dictionary;
@@ -827,17 +827,6 @@ static void init_type_metadata(Dictionary *metadata)
PUT(*metadata, "types", DICTIONARY_OBJ(types)); PUT(*metadata, "types", DICTIONARY_OBJ(types));
} }
static void init_api_level_metadata(Dictionary *metadata)
{
Dictionary version = ARRAY_DICT_INIT;
PUT(version, "current", INTEGER_OBJ(NVIM_API_CURRENT));
PUT(version, "compatibility", INTEGER_OBJ(NVIM_API_COMPATIBILITY));
#ifdef NVIM_API_PRERELEASE
PUT(version, "prerelease", BOOLEAN_OBJ(true));
#endif
PUT(*metadata, "api_level", DICTIONARY_OBJ(version));
}
/// Creates a deep clone of an object /// Creates a deep clone of an object
Object copy_object(Object obj) Object copy_object(Object obj)

View File

@@ -7,6 +7,7 @@
#include <assert.h> #include <assert.h>
#include <limits.h> #include <limits.h>
#include "nvim/api/private/helpers.h"
#include "nvim/vim.h" #include "nvim/vim.h"
#include "nvim/ascii.h" #include "nvim/ascii.h"
#include "nvim/iconv.h" #include "nvim/iconv.h"
@@ -2514,6 +2515,17 @@ bool has_vim_patch(int n)
return false; return false;
} }
Dictionary version_dict(void) {
Dictionary d = ARRAY_DICT_INIT;
PUT(d, "major", INTEGER_OBJ(NVIM_VERSION_MAJOR));
PUT(d, "minor", INTEGER_OBJ(NVIM_VERSION_MINOR));
PUT(d, "patch", INTEGER_OBJ(NVIM_VERSION_PATCH));
PUT(d, "api_level", INTEGER_OBJ(NVIM_API_LEVEL));
PUT(d, "api_compatible", INTEGER_OBJ(NVIM_API_LEVEL_COMPAT));
PUT(d, "api_prerelease", BOOLEAN_OBJ(NVIM_API_PRERELEASE));
return d;
}
void ex_version(exarg_T *eap) void ex_version(exarg_T *eap)
{ {
// Ignore a ":version 9.99" command. // Ignore a ":version 9.99" command.

View File

@@ -1,65 +0,0 @@
local helpers = require('test.functional.helpers')(after_each)
local lfs = require('lfs')
local mpack = require('mpack')
local clear, eq, neq = helpers.clear, helpers.eq, helpers.neq
local read_mpack_file = function(fname)
local fd = io.open(fname, 'rb')
local data = fd:read('*a')
fd:close()
local unpack = mpack.Unpacker()
return unpack(data)
end
-- ignore metadata in API function spec
local remove_function_metadata = function(fspec)
fspec['can_fail'] = nil
fspec['async'] = nil
fspec['method'] = nil
fspec['since'] = nil
fspec['deprecated_since'] = nil
fspec['receives_channel_id'] = nil
for idx,_ in ipairs(fspec['parameters']) do
fspec['parameters'][idx][2] = ''
end
end
clear()
local api_level = helpers.call('api_info')['api_level']
describe('api compatibility', function()
before_each(clear)
it("version metadata is sane", function()
local info = helpers.call('api_info')
local current = info['api_level']['current']
local compatibility = info['api_level']['compatibility']
neq(current, nil)
neq(compatibility, nil)
assert(current >= compatibility)
end)
for ver = api_level['compatibility'], api_level['current'] do
local path = 'test/functional/fixtures/api-info/' .. tostring(ver) .. '.mpack'
it('are backwards compatible with api level '..ver, function()
if lfs.attributes(path,"mode") ~= "file" then
pending("No fixture found, skipping test")
return
end
local old_api = read_mpack_file(path)
local api = helpers.call('api_info')
for _, fspec in ipairs(old_api['functions']) do
remove_function_metadata(fspec)
for _, fspec_new in ipairs(api['functions']) do
if fspec['name'] == fspec_new['name'] then
remove_function_metadata(fspec_new)
eq(fspec, fspec_new)
end
end
end
end)
end
end)

View File

@@ -0,0 +1,71 @@
local helpers = require('test.functional.helpers')(after_each)
local mpack = require('mpack')
local clear, funcs, eq = helpers.clear, helpers.funcs, helpers.eq
local function read_mpack_file(fname)
local fd = io.open(fname, 'rb')
local data = fd:read('*a')
fd:close()
local unpack = mpack.Unpacker()
return unpack(data)
end
-- Remove metadata that is not essential to backwards-compatibility.
local function remove_function_metadata(fspec)
fspec['can_fail'] = nil
fspec['async'] = nil
fspec['method'] = nil
fspec['since'] = nil
fspec['deprecated_since'] = nil
fspec['receives_channel_id'] = nil
for idx, _ in ipairs(fspec['parameters']) do
fspec['parameters'][idx][2] = '' -- Remove parameter name.
end
return fspec
end
describe("api_info()['version']", function()
before_each(clear)
it("returns API level", function()
local version = helpers.call('api_info')['version']
local current = version['api_level']
local compat = version['api_compatible']
eq("number", type(current))
eq("number", type(compat))
assert(current >= compat)
end)
it("returns Nvim version", function()
local version = helpers.call('api_info')['version']
local major = version['major']
local minor = version['minor']
local patch = version['patch']
eq("number", type(major))
eq("number", type(minor))
eq("number", type(patch))
eq(1, funcs.has("nvim-"..major.."."..minor.."."..patch))
eq(0, funcs.has("nvim-"..major.."."..minor.."."..(patch + 1)))
eq(0, funcs.has("nvim-"..major.."."..(minor + 1).."."..patch))
eq(0, funcs.has("nvim-"..(major + 1).."."..minor.."."..patch))
end)
it("api_compatible level is valid", function()
local api = helpers.call('api_info')
local compat = api['version']['api_compatible']
local path = 'test/functional/fixtures/api_level_'
..tostring(compat)..'.mpack'
-- Verify that the current API function signatures match those of the API
-- level for which we claim compatibility.
local old_api = read_mpack_file(path)
for _, fn_old in ipairs(old_api['functions']) do
for _, fn_new in ipairs(api['functions']) do
if fn_old['name'] == fn_new['name'] then
eq(remove_function_metadata(fn_old),
remove_function_metadata(fn_new))
end
end
end
end)
end)

View File

@@ -106,7 +106,7 @@ describe('api functions', function()
it('have metadata accessible with api_info()', function() it('have metadata accessible with api_info()', function()
local api_keys = eval("sort(keys(api_info()))") local api_keys = eval("sort(keys(api_info()))")
eq({'api_level', 'error_types', 'functions', 'types'}, api_keys) eq({'error_types', 'functions', 'types', 'version'}, api_keys)
end) end)
it('are highlighted by vim.vim syntax file', function() it('are highlighted by vim.vim syntax file', function()

View File

@@ -460,7 +460,7 @@ describe('msgpackparse() function', function()
eval(cmd) eval(cmd)
eval(cmd) -- do it again (try to force segfault) eval(cmd) -- do it again (try to force segfault)
local api_info = eval(cmd) -- do it again local api_info = eval(cmd) -- do it again
eq({'api_level', 'error_types', 'functions', 'types'}, api_info) eq({'error_types', 'functions', 'types', 'version'}, api_info)
end) end)
it('fails when called with no arguments', function() it('fails when called with no arguments', function()