CLANGD_PATHS = {};
NINJA_PATHS = {};
GDB_PATHS = {};
CMAKE_PATHS = {};
XMAKE_PATHS = {};
CCACHE_PATHS = {};
VCPKG_PATHS = {};
local OPENOCD_INFO = nil;
local PYOCD_INFO = nil;
local JLINK_INFO = nil;
local STLINK_INFO = {};
local THEAD_DEBUGSERVER_INFO = {};
local ARM_NONE_EABI_TOOLSETS = {};
local EDX_TOOLSETS_ROOT = (function()
		local edx_exe_path = edx.make_config_path("edx.exe", CFG_PATH_CWD);
		local toolset_lua_path = make_path(edx_exe_path, "../lua/toolset.lua");
		local edx_lua_path = make_path(edx_exe_path, "../lua/edx.lua");
		local toolsets_root = make_path(edx_exe_path, "../toolsets");
		local root = {};
		if utils:is_dir(toolsets_root) then
			table.insert(root, toolsets_root);
		end;
		local app_toolset_root = make_path(edx.app_path, [[toolsets]]);
		if utils:is_dir(app_toolset_root) and app_toolset_root ~= toolsets_root then
			table.insert(root, app_toolset_root);
		end;
		return root;
end)();

local clang_extra_flags = "-ftabstop=1";
local clang_cl_extra_flags = "/diagnostics:column";
local gcc_extra_flags = "-ftabstop=1";
local msvc_extra_flags = "/diagnostics:column";

local validate_path = function(path, path2)
	if utils:file_time(path) then
		return path;
	end;
	if path2 and utils:file_time(path2) then
		return path2;
	end;
	return nil;
end;


local append_valid_path = function(tab, path, path2)
	if path and utils:file_time(path) then
		table.insert(tab, path);
	end;
	if path2 and utils:file_time(path2) then
		table.insert(tab, path2);
	end;
end;

local function map_gcc_target(target,path)
	if path:find("\\msys") then
		target = target.."-msys"
	end;
	local gcc_target_map = {
		["x86_64-w64-mingw32"] = "mgw64";
		["i686-w64-mingw32"] = "mgw32";
		["x86_64-w64-mingw32-msys"] = "msys-mgw64";
		["i686-w64-mingw32-msys"] = "msys-mgw32";
	};
	local mapped_target = gcc_target_map[target];
	if mapped_target then
		return mapped_target;
	end;
	return target;
end;

function find_intel_tools(vs_info)
	local tool_types = {
		{
			tool_name_prefix="intel-oneapi_";
			reg_root = [[HKLM\SOFTWARE\WOW6432Node\Intel\1ASuites]];
			bin_x64_postfix = [[bin]];
			bin_x86_postfix = [[bin]];
			cxx_x64_name = "clang-cl.exe";
			cc_x64_name = "clang-cl.exe";
			cxx_x86_name = "clang-cl.exe;-m32";
			cc_x86_name = "clang-cl.exe;-m32";
		},
		{
			tool_name_prefix="intel-oneapi_";
			reg_root = [[HKLM\SOFTWARE\WOW6432Node\Intel\1ASuites]];
			bin_x64_postfix = [[bin]];
			bin_x86_postfix = [[bin]];
			cxx_x64_name = "../bin-llvm/clang-cl.exe";
			cc_x64_name = "../bin-llvm/clang-cl.exe";
			cxx_x86_name = "../bin-llvm/clang-cl.exe;-m32";
			cc_x86_name = "../bin-llvm/clang-cl.exe;-m32";
		},
		{
			tool_name_prefix="intel-icl_";
			reg_root = [[HKLM\SOFTWARE\WOW6432Node\Intel\Suites]];
			bin_x64_postfix = [[bin\intel64]];
			bin_x86_postfix = [[bin\intel64_ia32]];
			cxx_x64_name = "icl.exe";
			cc_x64_name = "icl.exe";
			cxx_x86_name = "icl.exe";
			cc_x86_name = "icl.exe";
		}
	};
	local versions = {}

	local find_intel_tools_impl = function(cfg)
		local intel_toolset_versions = utils:list_reg(cfg.reg_root);
		if intel_toolset_versions == nil or #intel_toolset_versions <=0 then
			return nil;
		end;
		for i, ver in ipairs(intel_toolset_versions) do
			if ver:lower() ~= "defaults" then
				local ver_id = utils:read_reg( make_path(cfg.reg_root, ver,[[Defaults\C++\EM64T\SubKey]]));
				if ver_id then
					local ver_info_reg_root = make_path(cfg.reg_root,ver,ver_id,[[C++]]);
					local toolset_name = utils:read_reg(make_path(ver_info_reg_root, "PlatformToolset"));
					if toolset_name then
						local toolset_dir = utils:read_reg(make_path(ver_info_reg_root, "ProductDir"));
						local toolset_include = utils:read_reg(make_path(ver_info_reg_root, [[EM64T_NATIVE\IncludeDir]]));
						local toolset_lib = utils:read_reg(make_path(ver_info_reg_root, [[EM64T_NATIVE\LibDir]]));
						toolset_include = toolset_include:gsub("%$%(ICInstallDir%)",toolset_dir);
						toolset_lib = toolset_lib:gsub("%$%(ICInstallDir%)",toolset_dir);
						local toolset_bin = make_path(toolset_dir, cfg.bin_x64_postfix);
						local toolset_bin_path = {toolset_bin};
						local toollset_include_path = {split_string(toolset_include,";")};
						local toollset_lib_path = {split_string(toolset_lib,";")};
						local tools_cxx = make_path(toolset_bin, cfg.cxx_x64_name);
						local tools_cc = make_path(toolset_bin, cfg.cc_x64_name);
						if utils:file_time(tools_cxx) ~= nil then
							if vs_info and vs_info.x64tools then
								for i, path in ipairs(vs_info.x64tools.include) do
									table.insert(toollset_include_path, path);
								end;
								for i, path in ipairs(vs_info.x64tools.lib) do
									table.insert(toollset_lib_path, path);
								end;
								for i, path in ipairs(vs_info.x64tools.bin) do
									table.insert(toolset_bin_path, path);
								end;
							end;

							table.insert(versions, {
									["type"] = "intel";
									["target"] = "x86_64-w64-msvc";
									name = cfg.tool_name_prefix.."x64-"..ver;
									home = toolset_dir;
									bin = toolset_bin_path;
									include = toollset_include_path;
									lib = toollset_lib_path;
									tools = {
										cxx = tools_cxx;
										cc = tools_cc;
										rc = vs_info.x64tools.tools.rc;
										["debugger"] = "windbg";
									};
									env = {
										["INTEL_TARGET_ARCH"] = "intel64";
									};
							});
						end;
					end;
				end;
				local ver_id = utils:read_reg(make_path(cfg.reg_root,ver,[[Defaults\C++\IA32\SubKey]]));
				if ver_id then
					local ver_info_reg_root = make_path(cfg.reg_root,ver,ver_id,[[C++]]);
					local toolset_name = utils:read_reg(make_path(ver_info_reg_root, "PlatformToolset"));
					if toolset_name then
						local toolset_dir = utils:read_reg(make_path(ver_info_reg_root, "ProductDir"));
						local toolset_include = utils:read_reg(make_path(ver_info_reg_root, [[IA32\IncludeDir]]));
						local toolset_lib = utils:read_reg(make_path(ver_info_reg_root, [[IA32\LibDir]]));
						toolset_include = toolset_include:gsub("%$%(ICInstallDir%)",toolset_dir);
						toolset_lib = toolset_lib:gsub("%$%(ICInstallDir%)",toolset_dir);
						local toolset_bin = make_path(toolset_dir, cfg.bin_x86_postfix);
						local toolset_bin_path = {toolset_bin};
						local toollset_include_path = {split_string(toolset_include,";")};
						local toollset_lib_path = {split_string(toolset_lib,";")};
						local tools_cxx_x64 = make_path(toolset_bin, cfg.cxx_x64_name);
						local tools_cxx = make_path(toolset_bin, cfg.cxx_x86_name);
						local tools_cc = make_path(toolset_bin, cfg.cc_x86_name);
						if utils:file_time(tools_cxx_x64) ~= nil then
							if vs_info and vs_info.x86tools then
								for i, path in ipairs(vs_info.x86tools.include) do
									table.insert(toollset_include_path, path);
								end;
								for i, path in ipairs(vs_info.x86tools.lib) do
									table.insert(toollset_lib_path, path);
								end;
								for i, path in ipairs(vs_info.x86tools.bin) do
									table.insert(toolset_bin_path, path);
								end;
							end;
							table.insert(versions, {
									["type"] = "intel";
									["target"] = "x86-w32-msvc";
									name = cfg.tool_name_prefix.."x86-"..ver;
									home = toolset_dir;
									bin = toolset_bin_path;
									include = toollset_include_path;
									lib = toollset_lib_path;
									tools = {
										cxx = tools_cxx;
										cc = tools_cc;
										rc = vs_info.x86tools.tools.rc;
										["debugger"] = "windbg";
									};
									env = {
										["INTEL_TARGET_ARCH"] = "ia32";
									};
							});
						end;
					end;
				end;
			end;
		end;
	end;

	for i, cfg in ipairs(tool_types) do
		find_intel_tools_impl(cfg);
	end;
	if #versions <= 0 then
		return nil;
	end;
	return versions;
end;

local function find_vs_paths()
	local vs_paths = {};
	for idx, val in ipairs(find_all_reg_val([[HKLM\SOFTWARE\Classes\]],"VisualStudio.c.",[[DefaultIcon\]])) do
		local path = string.gsub(val, [["(.*)\Common.*]], "%1");
		table.insert(vs_paths, path);
	end;
	return vs_paths;
end;

local function prepare_vs_info_by_ver(ver)
	local vs_info = {};

	local try_get_pair = function(key, val)
		if key then
			return { key; val };
		end
		return nil;
	end;
	if ver == nil then
		ver = 17;
	end;

	local info = try_get_pair(utils:get_program_id(string.format("visualstudio.solution.%d.0", ver)), ver);

	if info == nil then
		return nil
	end

	vs_info.version = info[2];
	local vs_devenv = utils:read_reg(string.format([[HKLM\SOFTWARE\WOW6432Node\Classes\CLSID\%s\LocalServer32\]], info[1])) or utils:read_reg(string.format([[HKLM\SOFTWARE\Classes\CLSID\%s\LocalServer32\]], info[1]));
	if vs_devenv == nil then
		return nil;
	end

	vs_devenv = string.gsub(vs_devenv, "\"", "");
	vs_info.root = make_path(vs_devenv, [[../../../]]);

	return vs_info;
end;

local function prepare_winsdk_info()
	local sdk_path = utils:read_reg([[HKLM\SOFTWARE\WOW6432Node\Microsoft\Microsoft SDKs\Windows\v10.0\InstallationFolder]]) or utils:read_reg([[HKLM\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0\InstallationFolder]]);
	if sdk_path == nil then
		return nil;
	end;
	local def_sdk_version = utils:read_reg([[HKLM\SOFTWARE\WOW6432Node\Microsoft\Microsoft SDKs\Windows\v10.0\ProductVersion]]) or utils:read_reg([[HKLM\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0\ProductVersion]]);

	local sdk_lib_vers = utils:list_dir(make_path(sdk_path, "Lib", "10.*"), true) or {};
	local sdk_inc_vers = utils:list_dir(make_path(sdk_path, "Include", "10.*"), true) or {};
	local sdk_bin_vers = utils:list_dir(make_path(sdk_path, "Bin", "10.*"), true) or {};

	local valid_vers = {};

	for idx, ver in ipairs(sdk_lib_vers) do
		valid_vers[ver] = 1;
	end;

	for idx, ver in ipairs(sdk_inc_vers) do
		if valid_vers[ver] then
			valid_vers[ver] = valid_vers[ver] + 1;
		else
			valid_vers[ver] = 1;
		end;
	end;

	local vers = {};
	for idx, ver in ipairs(sdk_bin_vers) do
		if valid_vers[ver] == 2 then
			local sdk_path_base = make_path(sdk_path, "Bin", ver);
			local rc_path_x64 = make_path(sdk_path_base, "x64", "rc.exe");
			local mt_path_x64 = make_path(sdk_path_base, "x64", "mt.exe");
			local rc_path_x86 = make_path(sdk_path_base, "x86", "rc.exe");
			local mt_path_x86 = make_path(sdk_path_base, "x86", "mt.exe");
			if utils:file_time(rc_path_x64) and utils:file_time(mt_path_x64) then
				table.insert(vers, ver);
			end;
		end;
	end;

	table.sort(vers);
	if not valid_vers[def_sdk_version] then
		def_sdk_version = vers[#vers];
	end;
	return {
		["path"] = sdk_path;
		["version"] = def_sdk_version;
		["inc"] = def_sdk_version;
		["lib"] = def_sdk_version;
		["vers"] = vers;
	};
end;

function get_vs_info(ver, winsdk)
	local vs_info;
	if type(ver) == "table" then
		vs_info = ver;
	else
		vs_info = prepare_vs_info_by_ver(ver);
	end;

	if vs_info == nil or vs_info.root == nil then
		return nil;
	end;

	vs_info.sdk = winsdk;
	local vs_root = vs_info.root;
	local vc_versions = utils:list_dir(make_path(vs_root, [[VC\Tools\MSVC]]), true) or {};
	local _x64tools = {};
	local _x64vers = {};
	local _x86tools = {};
	local _x86vers = {};
	if #vc_versions == 0 then
		return nil;
	end;

	local _sdks = {};
	local _sdkvers = {};
	if winsdk == nil or winsdk.path == nil then
	elseif winsdk.vers then
		local sdk_path = winsdk.path;
		for idx, ver in ipairs(winsdk.vers) do
			_sdks[ver] = {
				["path"] = sdk_path;
				["version"] = ver;
				["inc"] = ver;
				["lib"] = ver;
			};
			table.insert(_sdkvers, ver);
		end;
	else
		_sdks[winsdk.version] = winsdk;
		table.insert(_sdkvers, winsdk.version);
	end;

	if vs_info.version == 16 or vs_info.version == 17 then
		local vs_name = "vs"..vs_info.version;
		if vs_info.version == 16 then
			vs_name = "vs2019"
		elseif vs_info.version == 17 then
			vs_name = "vs2022"
		end;

		for idx, vc_version in ipairs(vc_versions) do
			local paths_x64 = {
				make_path(vs_info.root, [[VC\Tools\MSVC]], vc_version, [[bin\Hostx64\x64]], "cl.exe");
				make_path(vs_info.root, [[VC\Tools\MSVC]], vc_version, [[bin\Hostx86\x64]], "cl.exe");
			};

			local paths_x86 = {
				make_path(vs_info.root, [[VC\Tools\MSVC]], vc_version, [[bin\Hostx64\x86]], "cl.exe");
				make_path(vs_info.root, [[VC\Tools\MSVC]], vc_version, [[bin\Hostx86\x86]], "cl.exe");
			};

			for i, p in ipairs(paths_x64) do
				if utils:file_time(p) ~= nil then
					_x64tools[vc_version] = {
						["type"] = "msvc";
						["target"] = "x86_64-w64-msvc";
						["name"] = vs_name.."-x64";
						["version"] = vc_version;
						["home"] = vs_info.root;
						["bin"] = { make_path(p, "..") };
						["include"] = { make_path(p, "../../../../include") };
						["lib"] = { make_path(p, "../../../../lib/x64") };
						tools = {
							["cc"] = p..";"..msvc_extra_flags;
							["cxx"] = p..";"..msvc_extra_flags;
							["msvc"] = true;
							["debugger"] = "windbg";
						}
					};
					local atlmfc_path = make_path(p, "../../../../atlmfc");
					if utils:file_time(atlmfc_path) then
						table.insert(_x64tools[vc_version].include, make_path(atlmfc_path, "include"));
						table.insert(_x64tools[vc_version].lib, make_path(atlmfc_path, "lib/x64"));
					end;
					table.insert(_x64vers, vc_version);
					vs_info.x64tools =_x64tools[vc_version];
					break;
				end;
			end;
			for i, p in ipairs(paths_x86) do
				if utils:file_time(p) ~= nil then
					_x86tools[vc_version] = {
						["type"] = "msvc";
						["target"] = "x86-w32-msvc";
						["name"] = vs_name.."-x86";
						["version"] = vc_version;
						["home"] = vs_info.root;
						["bin"] = { make_path(p, "..") };
						["include"] = { make_path(p, "../../../../include") };
						["lib"] = { make_path(p, "../../../../lib/x86") };
						tools = {
							["cc"] = p..";"..msvc_extra_flags;
							["cxx"] = p..";"..msvc_extra_flags;
							["msvc"] = true;
							["debugger"] = "windbg";
						}
					};
					local atlmfc_path = make_path(p, "../../../../atlmfc");
					if utils:file_time(atlmfc_path) then
						table.insert(_x86tools[vc_version].include, make_path(atlmfc_path, "include"));
						table.insert(_x86tools[vc_version].lib, make_path(atlmfc_path, "lib/x86"));
					end;
					table.insert(_x86vers, vc_version);
					vs_info.x86tools = _x86tools[vc_version];
					break;
				end;
			end;
		end;
	end;

	local function make_vs_tools(vc_version, arch, sdk)
		if sdk == nil then
			sdk = winsdk;
		elseif type(sdk) == "string" then
			sdk = _sdks[sdk];
		end;
		local arch_tools = {
			["x86"] = _x86tools;
			["x64"] = _x64tools;
		};
		local arch_vers = {
			["x86"] = _x86vers;
			["x64"] = _x64vers;
		};
		local cur_arch_tools = arch_tools[arch];
		if sdk ~= nil and sdk.path and cur_arch_tools ~= nil then
			local sdk_path = sdk.path;
			local sdk_version = sdk.version;
			local sdk_inc = sdk.inc;
			local sdk_lib = sdk.lib;
			local sdk_bin_root = sdk_version and make_path(sdk_path, "Bin", sdk_version);
			local vs_tools = table.deep_clone(cur_arch_tools[vc_version]);
			vs_tools.vers = arch_vers[arch];
			vs_tools.sdks = _sdkvers;
			vs_tools.sdk = sdk_version;
			if sdk_version then
				table.insert(vs_tools.bin, make_path(sdk_path, "Bin", sdk_version, arch));
			end;
			if sdk_inc then
				table.insert(vs_tools.include, make_path(sdk_path, "Include", sdk_inc, "ucrt"));
				table.insert(vs_tools.include, make_path(sdk_path, "Include", sdk_inc, "um"));
				table.insert(vs_tools.include, make_path(sdk_path, "Include", sdk_inc, "shared"));
				table.insert(vs_tools.include, make_path(sdk_path, "Include", sdk_inc, "winrt"));
				table.insert(vs_tools.include, make_path(sdk_path, "Include", sdk_inc, "cppwinrt"));
			end;
			if sdk_lib then
				table.insert(vs_tools.lib, make_path(sdk_path, "Lib", sdk_lib, "ucrt", arch));
				table.insert(vs_tools.lib, make_path(sdk_path, "Lib", sdk_lib, "um", arch));
			end;
			if vs_tools.tools then
				vs_tools.tools["rc"] = sdk_bin_root and make_path(sdk_bin_root, arch, "rc.exe");
			end;
			vs_tools.switch_version = function(self, ver, sdk)
				local new_tools = make_vs_tools(ver, arch, sdk);
				for k, v in pairs(new_tools) do
					self[k] = v;
				end;
			end;
			return vs_tools;
		end;
		return nil;
	end;

	if vs_info.x86tools ~= nil then
		vs_info.x86tools = make_vs_tools(vs_info.x86tools.version, "x86", winsdk);
	end;
	if vs_info.x64tools ~= nil then
		vs_info.x64tools = make_vs_tools(vs_info.x64tools.version, "x64", winsdk);
	end;
	return vs_info;
end;

function find_windows_terminal()
	local wt_root = find_reg_val([[HKCR\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel\Repository\Packages]],"Microsoft.WindowsTerminal", "PackageRootFolder");

	if wt_root == nil then
		return nil;
	end;
	local wt = make_path(wt_root, "wt.exe");
	if utils:file_time(wt) then
		return wt, wt_root, "wt.exe";
	end;
	return;
end;

function find_clang_tools_in_path(llvm_path, vs_info, prefix, single_version)
	if llvm_path == nil or utils:file_time(llvm_path) == nil then
		return;
	end;
	if prefix == nil then
		prefix = "";
	end;
	local clang_bin = make_path(llvm_path, "Bin");
	local clang_path = make_path(clang_bin, "clang.exe");
	local clang_cl_path = make_path(clang_bin, "clang-cl.exe");
	local clangd_path = make_path(clang_bin, "clangd.exe");
	local clangxx_path = make_path(clang_bin, "clang++.exe");
	local clang_lib_path = make_path(llvm_path, [[lib\clang]]);
	local clang_ver = utils:list_dir(clang_lib_path);

	local x64tools = (vs_info and vs_info.x64tools) or {bin = {}, include = {}, lib = {}, tools={rc=""}};
	local x86tools = (vs_info and vs_info.x86tools) or {bin = {}, include = {}, lib = {}, tools={rc=""}};

	if clang_ver then
		local is_x64 = clang_ver and (utils:list_dir(make_path(clang_lib_path, clang_ver, [[lib\windows\*x86_64.lib]])) ~= nil);
		local has_clang_cl = (utils:file_time(clang_cl_path) ~= nil);

		local clang_tools = {};

		local tools = {
			["clang"] = (utils:file_time(clang_path) ~= nil);
			["clang++"] = (utils:file_time(clangxx_path) ~= nil);
			["lldb"] = (utils:file_time(make_path(clang_bin, "lldb.exe")) ~= nil);
			["clang-format"] = (utils:file_time(make_path(clang_bin, "clang-format.exe")) ~= nil);
			["clang-tidy"] = (utils:file_time(make_path(clang_bin, "clang-tidy.exe")) ~= nil);
			["debugger"] = "windbg";
		};

		if utils:file_time(clangd_path) then
			tools["clangd"] = clangd_path;
			table.insert(CLANGD_PATHS, clangd_path);
		end;

		if not vs_info then
			return nil;
		end;

		local clang_ver_suffix="-"..clang_ver;
		if is_x64 then
			clang_tools[1] = {
				["type"] = "clang-msvc";
				["target"] = "x86_64-w64-msvc";
				["name"] = prefix.."clang-x64"..clang_ver_suffix;
				home = llvm_path;
				bin = table.iclone({clang_bin}, x64tools.bin);
				include = x64tools.include;
				lib = x64tools.lib;
				["tools"] = table.clone(tools,{
						["cc"] = clang_path..[[;]]..clang_extra_flags;
						["cxx"] = clangxx_path..[[;]]..clang_extra_flags;
						rc = x64tools.tools.rc;
				});
			};
			local dummy = has_clang_cl and table.insert(clang_tools, {
					["type"] = "clang-cl";
					["target"] = "x86_64-w64-msvc";
					["name"] = prefix.."clang-cl-x64"..clang_ver_suffix;
					home = llvm_path;
					bin = table.iclone({clang_bin}, x64tools.bin);
					include = x64tools.include;
					lib = x64tools.lib;
					["tools"] = table.clone(tools,{
							["cc"] = clang_cl_path..[[;]]..clang_cl_extra_flags;
							["cxx"] = clang_cl_path..[[;]]..clang_cl_extra_flags;
							rc = x64tools.tools.rc;
							["msvc"] = true;
					});
			});
			if not single_version then
				table.insert(clang_tools,{
						["type"] = "clang-msvc";
						["target"] = "x86-w32-msvc";
						["name"] = prefix.."clang-x86"..clang_ver_suffix;
						home = llvm_path;
						bin = table.iclone({clang_bin}, x86tools.bin);
						include = x86tools.include;
						lib = x86tools.lib;
						["tools"] = table.clone(tools,{
								["cc"] = clang_path..[[;-m32 ]]..clang_extra_flags;
								["cxx"] = clangxx_path..[[;-m32 ]]..clang_extra_flags;
								rc = x86tools.tools.rc;
						});
				});
				local dummy = has_clang_cl and table.insert(clang_tools, {
						["type"] = "clang-cl";
						["target"] = "x86-w32-msvc";
						["name"] = prefix.."clang-cl-x86"..clang_ver_suffix;
						home = llvm_path;
						bin = table.iclone({clang_bin}, x86tools.bin);
						include = x86tools.include;
						lib = x86tools.lib;
						["tools"] = table.clone(tools,{
								["cc"] = clang_cl_path..[[;-m32 ]]..clang_cl_extra_flags;
								["cxx"] = clang_cl_path..[[;-m32 ]]..clang_cl_extra_flags;
								rc = x86tools.tools.rc;
								["msvc"] = true;
						});
				});
			end;
		else
			clang_tools[1] = {
				["type"] = "clang-msvc";
				["target"] = "x86-w32-msvc";
				["name"] = prefix.."clang-x86"..clang_ver_suffix;
				home = llvm_path;
				bin = table.iclone({clang_bin}, x86tools.bin);
				include = x86tools.include;
				lib = x86tools.lib;
				["tools"] = table.clone(tools,{
						["cc"] = clang_path..[[;]]..clang_extra_flags;
						["cxx"] = clangxx_path..[[;]]..clang_extra_flags;
						rc = x86tools.tools.rc;
				});
			};
			local dummy = has_clang_cl and table.insert(clang_tools, {
					["type"] = "clang-cl";
					["target"] = "x86-w32-msvc";
					["name"] = prefix.."clang-x86"..clang_ver_suffix;
					home = llvm_path;
					bin = table.iclone({clang_bin}, x86tools.bin);
					include = x86tools.include;
					lib = x86tools.lib;
					["tools"] = table.clone(tools,{
							["cc"] = clang_cl_path..[[;]]..clang_cl_extra_flags;
							["cxx"] = clang_cl_path..[[;]]..clang_cl_extra_flags;
							rc = x86tools.tools.rc;
							["msvc"] = true;
					});
			});
		end;
		return clang_tools;
	end;
	return nil;
end;

function find_clang_tools(vs_info)
	local llvm_path = utils:read_reg([[HKLM\SOFTWARE\WOW6432Node\LLVM\LLVM\]]) or utils:read_reg([[HKLM\SOFTWARE\LLVM\LLVM\]]);
	local clang_tools = find_clang_tools_in_path(llvm_path, vs_info);
	local clang_tools_x86 = find_clang_tools_in_path(make_path(vs_info.root, [[VC\Tools\Llvm]]), vs_info, "ms-", true);
	local clang_tools_x64 = find_clang_tools_in_path(make_path(vs_info.root, [[VC\Tools\Llvm\x64]]), vs_info, "ms-", true);
	local tools_map = {};
	local tools = {};
	local merge_tools = function(src_tools)
		if not src_tools then
			return;
		end;
		for idx, t in ipairs(src_tools) do
			if tools_map[t.name] == nil then
				tools_map[t.name] = t;
				table.insert(tools, t);
			end;
		end;
	end;
	merge_tools(clang_tools);
	merge_tools(clang_tools_x86);
	merge_tools(clang_tools_x64);
	if #tools > 0 then
		return tools;
	end;
	return nil;
end;

local function append_toolset(toolsets, toolset)
	if toolset.target == "arm-none-eabi" then
		local prefix = "arm-none-eabi-";
		if toolset.tools then
			toolset.tools["rc"] = nil;
			if toolset.tools.clang then
				prefix = "clang-"..prefix;
			else
				prefix = "gcc-"..prefix;
			end;
		end;
		ARM_NONE_EABI_TOOLSETS[prefix..toolset.version] = toolset;
	else
		table.insert(toolsets, toolset);
	end;
end;

function find_mingw_tools_in_path(bin_path, toolsets)
	if toolsets == nil then
		toolsets = {};
	end;
	local gxx_path = make_path(bin_path, "g++.exe");
	local gcc_path = make_path(bin_path, "gcc.exe");
	local windres_path = make_path(bin_path, "windres.exe");
	local clang_path = make_path(bin_path, "clang.exe");
	local clangd_path = make_path(bin_path, "clangd.exe");
	local clangxx_path = make_path(bin_path, "clang++.exe");
	local ninja_path = make_path(bin_path, "ninja.exe");
	local gdb_path = make_path(bin_path, "gdb.exe");
	local cmake_path = make_path(bin_path, "cmake.exe");
	local ccache_path = make_path(bin_path, "ccache.exe");
	local has_clangd = utils:file_time(clangd_path) ~= nil;
	local has_cmake = utils:file_time(cmake_path) ~= nil;
	local has_gdb = utils:file_time(gdb_path) ~= nil;
	local has_clang = utils:file_time(clang_path) ~= nil;
	local has_ninja = (utils:file_time(ninja_path) ~= nil);
	local has_ccache = (utils:file_time(ccache_path) ~= nil);
	if has_ccache then
		table.insert(CCACHE_PATHS, ccache_path);
	end;
	if has_ninja then
		table.insert(NINJA_PATHS, ninja_path);
	else
		ninja_path = nil;
	end;
	if has_gdb then
		table.insert(GDB_PATHS, gdb_path);
	else
		gdb_path = nil;
	end;
	if has_cmake then
		table.insert(CMAKE_PATHS, cmake_path);
	else
		cmake_path = nil;
	end;
	if has_clangd then
		table.insert(CLANGD_PATHS, clangd_path);
	end;
	local all_gcc_types = utils:list_dir(make_path(bin_path, "../lib/gcc/*"), true) or {};
	for idx, gcc_type in ipairs(all_gcc_types) do
		local gxx_path = make_path(bin_path, gcc_type.."-g++.exe");
		local gcc_path = make_path(bin_path, gcc_type.."-gcc.exe");
		local has_gcc = utils:file_time(gcc_path) ~= nil;
		local is_local_gcc = false;
		if string.match(gcc_type, ".*mingw.*") then
			is_local_gcc = true;
		end;
		if has_gcc then
			local ext_flags="";
			if gcc_type == "arm-none-eabi" then
				ext_flags = ";-mthumb -mcpu=cortex-m0 -specs=nosys.specs";
			else
				ext_flags = ";"..gcc_extra_flags;
			end;
			local mingw_tools = {
				["type"] = "mingw";
				["target"] = gcc_type;
				bin = { bin_path };
				include = {};
				lib = {};
				tools = {
					["gcc"] = true;
					["g++"] = (utils:file_time(gxx_path) ~= nil);
					["gdb"] = gdb_path;
					["make"] = (utils:file_time(make_path(bin_path, "make.exe")) ~= nil);
					["cmake"] = cmake_path;
					["ninja"] = ninja_path;
					["cc"] = gcc_path..ext_flags;
					["cxx"] = gxx_path..ext_flags;
					["rc"] = windres_path;
					["debugger"] = "gdb/mi";
				};
			};
			if is_local_gcc then
				mingw_tools.include = { make_path(bin_path, "../include") };
				mingw_tools.lib = { make_path(bin_path, "../lib") };
			end;
			local gcc_version = nil;
			local cxx_inc_path = nil;
			if gcc_type ~= nil then
				gcc_version = utils:list_dir(make_path(bin_path, "../lib/gcc", gcc_type));
			end
			if gcc_version ~= nil then
				cxx_inc_path = make_path(bin_path, "../include/c++", gcc_version);
				gcc_pretty_print = make_path(bin_path, "../share/gcc-"..gcc_version, "python");
				if utils:file_time(gcc_pretty_print) == nil then
					gcc_pretty_print = nil;
				end;

				local insert_32 = 0;
				if utils:file_time(cxx_inc_path) ~= nil then
					table.insert(mingw_tools.include, cxx_inc_path);
					table.insert(mingw_tools.include, make_path(cxx_inc_path, gcc_type));
					insert_32 = #mingw_tools.include;
					table.insert(mingw_tools.include, make_path(cxx_inc_path, gcc_type, "backward"));
					local gcc_inc = make_path(bin_path, "../lib/gcc", gcc_type, gcc_version, "include");
					table.insert(mingw_tools.include, gcc_inc);
					table.insert(mingw_tools.include, gcc_inc.."-fixed");
				end;
				table.insert(mingw_tools.include, make_path(bin_path, "..", gcc_type, "include"));
				table.insert(mingw_tools.lib, make_path(bin_path, "..", gcc_type, "lib"));

				local gcc_version_suffix = "-".. gcc_version:gsub("%.0$","");
				local toolset_name = "gcc-".. map_gcc_target(gcc_type,bin_path)..gcc_version_suffix;
				mingw_tools["name"] = toolset_name;
				mingw_tools["home"] = make_path(bin_path, "..");
				mingw_tools.tools["gcc-pretty-print"] = gcc_pretty_print;
				mingw_tools["version"] = gcc_version;
				mingw_tools["is_local_target"] = is_local_gcc;
				local gcc_version_value = tonumber(gcc_version:match("(%d+%.%d*)"));
				if gcc_version_value >= 13 then
					mingw_tools["encoding"] = "utf-8";
				end;

				append_toolset(toolsets, mingw_tools);

				local has_32_bits = utils:list_dir(make_path(bin_path, "..", gcc_type, [[lib32\crt1.*]])) ~= nil;
				if has_32_bits and (not gcc_path:match("^.*\\msys.*$")) then
					local include_32 = table.clone(mingw_tools.include);
					if insert_32 > 0 then
						include_32[insert_32] = make_path(include_32[insert_32], "32");
					end;
					mingw_tools_32 = {
						["type"] = "mingw";
						["target"] = "x86-w32-mingw32";
						name = toolset_name .. "-m32";
						home = make_path(bin_path, "..");
						bin = mingw_tools.bin;
						version = mingw_tools.version;
						is_local_target = mingw_tools.is_local_target;
						include = include_32;
						lib = {};
						tools = table.clone(mingw_tools.tools,{
								["cc"] = gcc_path..";-m32 "..gcc_extra_flags;
								["cxx"] = gxx_path..";-m32 "..gcc_extra_flags;
								["rc"] = windres_path..";-F pe-i386";
						});
					};
					for i, name in ipairs(mingw_tools.lib) do
						mingw_tools_32.lib[i] = name.."32";
					end;
					if gcc_version_value >= 13 then
						mingw_tools_32["encoding"] = "utf-8";
					end;
					append_toolset(toolsets, mingw_tools_32);
				end;
			end;
		end;
		if has_clang and gcc_type:find("mingw") then
			local clang_mingw_tools = {
				["type"] = "clang-mingw";
				["target"] = gcc_type;
				bin = { bin_path };
				include = { make_path(bin_path, "../include") };
				lib = { make_path(bin_path, "../lib") };
				tools = {
					["gcc"] = true;
					["clang"] = true;
					["g++"] = (utils:file_time(gxx_path) ~= nil);
					["gdb"] = gdb_path;
					["make"] = (utils:file_time(make_path(bin_path, "make.exe")) ~= nil);
					["cmake"] = cmake_path;
					["ninja"] = ninja_path;
					["cc"] = clang_path..[[;]]..clang_extra_flags;
					["cxx"] = clangxx_path..[[;]]..clang_extra_flags;
					["rc"] = windres_path;
					["debugger"] = "auto";
				};
			};

			if utils:file_time(clangd_path) then
				clang_mingw_tools.tools["clangd"] = clangd_path;
				table.insert(CLANGD_PATHS, clangd_path);
			end;

			local gcc_version = nil;
			local clang_version = nil;
			local cxx_inc_path = nil;
			if gcc_type ~= nil then
				gcc_version = utils:list_dir(make_path(bin_path, "../lib/gcc", gcc_type));
				clang_version = utils:list_dir(make_path(bin_path, "../lib/clang"));
			end
			if gcc_version ~= nil and clang_version ~= nil then
				cxx_inc_path = make_path(bin_path, "../include/c++", gcc_version);
				gcc_pretty_print = make_path(bin_path, "../share/gcc-"..gcc_version, "python");
				if utils:file_time(gcc_pretty_print) == nil then
					gcc_pretty_print = nil;
				end;
				table.insert(clang_mingw_tools.include, make_path(bin_path, "..", gcc_type, "include"));
				table.insert(clang_mingw_tools.lib, make_path(bin_path, "..", gcc_type, "lib"));
				table.insert(clang_mingw_tools.include, cxx_inc_path);
				table.insert(clang_mingw_tools.include, make_path(bin_path, "../lib/clang", clang_version, "include"));
				table.insert(clang_mingw_tools.include, make_path(cxx_inc_path, gcc_type));

				local gcc_version_suffix = "-".. clang_version;
				local toolset_name = "clang-".. map_gcc_target(gcc_type,bin_path)..gcc_version_suffix;
				clang_mingw_tools["name"] = toolset_name;
				clang_mingw_tools["home"] = make_path(bin_path, "..");
				clang_mingw_tools.tools["gcc-pretty-print"] = gcc_pretty_print;
				append_toolset(toolsets, clang_mingw_tools);

				local has_32_bits = utils:list_dir(make_path(bin_path, "..", gcc_type, [[lib32\crt1.*]])) ~= nil;
				if has_32_bits then
					clang_mingw_tools_32 = {
						["type"] = "clang-mingw";
						["target"] = gcc_type;
						name = toolset_name .. "-m32";
						home = make_path(bin_path, "..");
						bin = clang_mingw_tools.bin;
						include = clang_mingw_tools.include;
						lib = {};
						tools = table.clone(clang_mingw_tools.tools,{
								["cc"] = clang_path..";-m32 "..clang_extra_flags;
								["cxx"] = clangxx_path..";-m32 "..clang_extra_flags;
								["rc"] = windres_path..";-F pe-i386";
						});
						["debugger"] = "auto";
					};
					for i, name in ipairs(clang_mingw_tools.lib) do
						clang_mingw_tools_32.lib[i] = name.."32";
					end;
					append_toolset(toolsets, clang_mingw_tools_32);
				end;
			end ;
		end;
	end;
	return toolsets;
end;

function find_mingw_tools()
	-- [[HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{9bbd9d0c-a70b-4184-8f73-bac63cc23153}]]
	local toolsets = {};
	local mingw_roots = {make_path(edx.app_path, "bin")};
	for i,root in ipairs(utils:list_dir(make_path(edx.app_path, [[\mingw*]]), true) or {}) do
		if root then
			table.insert(mingw_roots, make_path(edx.app_path, root, "bin"));
		end;
	end;

	for i, toolset_root in ipairs(EDX_TOOLSETS_ROOT) do
		for i,root in ipairs(utils:list_dir(make_path(toolset_root, [[mingw*]]), true) or {}) do
			if root then
				local bin_path = make_path(toolset_root, root, "bin");
				table.insert(mingw_roots, bin_path);
				if root == "mingw-tools" then
					CMAKE_LANGUAGE_SERVER = utils:list_dir(make_path(bin_path, "neocmakelsp-*.exe"));
					if CMAKE_LANGUAGE_SERVER then
						CMAKE_LANGUAGE_SERVER = make_path(bin_path, CMAKE_LANGUAGE_SERVER);
					end;
					-- LUALSP_LANGUAGE_SERVER = utils:list_dir(make_path(bin_path, "lualsp*.exe"));
					-- if LUALSP_LANGUAGE_SERVER then
					-- 	LUALSP_LANGUAGE_SERVER = make_path(bin_path, LUALSP_LANGUAGE_SERVER);
					-- end;
				end;
			end;
		end;
	end;

	for i, drive in ipairs({ "C:", "D:", "E:", "F:" }) do
		for i,root in ipairs(utils:list_dir(make_path(drive, [[\mingw*]]), true) or {}) do
			if root then
				table.insert(mingw_roots, make_path(drive, root,"bin"));
			end;
		end;
		for i,root in ipairs(utils:list_dir(make_path(drive, [[\msys*]]), true) or {}) do
			if root then
				table.insert(mingw_roots, make_path(drive, root,[[mingw32\bin]]));
				table.insert(mingw_roots, make_path(drive, root,[[mingw64\bin]]));
				table.insert(mingw_roots, make_path(drive, root,[[ucrt64\bin]]));
			end;
		end;
	end;

	mingw_roots = table.unique(mingw_roots);
	for j, bin_path in ipairs(mingw_roots) do
		find_mingw_tools_in_path(bin_path, toolsets);
	end;
	return toolsets;
end;

function find_llvm_mingw_tools_in_path(bin_path, toolsets)
	if toolsets == nil then
		toolsets = {};
	end;
	local target_map = {
		["aarch64"] = "aarch64-w64-mingw";
		["armv7"] = "armv7-w32-mingw";
		["i686"] = "i686-w32-mingw";
		["x86_64"] = "x86_64-w64-mingw";
	};
	for idx, host_type in ipairs({"aarch64", "armv7", "i686", "x86_64"}) do
		local type_prefix = host_type..[[-w64-mingw32]];
		local clang_path = make_path(bin_path, type_prefix.."-clang.exe");
		local clangd_path = make_path(bin_path, "clangd.exe");
		local clangxx_path = make_path(bin_path, type_prefix.."-clang++.exe");
		local windres_path = make_path(bin_path, type_prefix.."-windres.exe");
		local lldb_path = make_path(bin_path, type_prefix.."-lldb.exe");
		local lldb_mi_path = make_path(bin_path, "lldb-mi.exe");
		local ninja_path = make_path(bin_path, "ninja.exe");
		local gmake_path = make_path(bin_path, "make.exe");
		local gdb_path = make_path(bin_path, "gdb.exe");
		local libcxx_printers_path = make_path(bin_path, "../share/libcxx/printers.py");
		local debugger_type = "auto";
		if utils:file_time(lldb_mi_path) == nil then
			lldb_mi_path = nil;
			-- debugger_type = "windbg";
		end;

		local has_clang = utils:file_time(clang_path);
		if has_clang ~= nil then
			local clang_tools = {
				["type"] = "llvm-mingw";
				["target"] = target_map[host_type];
				bin = { bin_path, make_path(bin_path, "..", type_prefix, "bin") };
				include = { make_path(bin_path, "../include") };
				lib = { make_path(bin_path, "../lib") };
				tools = {
					["clang"] = true;
					["clang++"] = (utils:file_time(clangxx_path) ~= nil);
					["lldb"] = lldb_path;
					["gdb"] = (utils:file_time(gdb_path) ~= nil) and gdb_path or nil;
					["make"] = (utils:file_time(gmake_path) ~= nil) and gmake_path or nil;
					["ninja"] = (utils:file_time(ninja_path) ~= nil) and ninja_path or nil;
					["clangd"] = (utils:file_time(clangd_path) ~= nil) and clangd_path or nil;
					["cc"] = clang_path..[[;]]..clang_extra_flags;
					["cxx"] = clangxx_path..[[;]]..clang_extra_flags;
					["rc"] = windres_path;
					["debugger"] = debugger_type;
					["libcxx-pretty-print"] = (utils:file_time(libcxx_printers_path) ~= nil) and libcxx_printers_path or nil;
				};
			};

			if utils:file_time(clangd_path) then
				clang_tools.tools["clangd"] = clangd_path;
				table.insert(CLANGD_PATHS, clangd_path);
			end;

			local cxx_version = utils:list_dir(make_path(bin_path, "../include/c++"));
			local clang_version = utils:list_dir(make_path(bin_path, "../lib/clang"));
			if cxx_version ~= nil and clang_version ~= nil then
				cxx_inc_path = make_path(bin_path, "../include/c++", cxx_version);
				table.insert(clang_tools.lib, make_path(bin_path, "..", type_prefix, "lib"));
				table.insert(clang_tools.include, cxx_inc_path);
				table.insert(clang_tools.include, make_path(bin_path, "../lib/clang", clang_version, "include"));

				local toolset_name = "clang-mgw-"..host_type.."-"..clang_version;
				clang_tools["name"] = toolset_name;
				clang_tools["home"] = make_path(bin_path, "..");
				append_toolset(toolsets, clang_tools);
			end ;
		end;
	end;
	return toolsets;
end;

function find_llvm_mingw_tools()
	local toolsets = {};
	local mingw_roots = {};
	for i, drive in ipairs({ "C:", "D:", "E:", "F:" }) do
		for i,root in ipairs(utils:list_dir(make_path(drive, [[\llvm-mingw*]]), true) or {}) do
			if root then
				table.insert(mingw_roots, make_path(drive, root,"bin"));
			end;
		end;
	end;

	for i, toolset_root in ipairs(EDX_TOOLSETS_ROOT) do
		for i,root in ipairs(utils:list_dir(make_path(toolset_root, [[llvm-mingw*]]), true) or {}) do
			if root then
				table.insert(mingw_roots, make_path(toolset_root, root, "bin"));
			end;
		end;
	end;

	mingw_roots = table.unique(mingw_roots);
	
	for j, bin_path in ipairs(mingw_roots) do
		find_llvm_mingw_tools_in_path(bin_path, toolsets);
	end;
	return toolsets;
end;

function find_llvm_msys2_tools_in_path(bin_path, toolsets)
	if toolsets == nil then
		toolsets = {};
	end;
	for idx, host_type in ipairs({"aarch64", "armv7", "i686", "x86_64"}) do
		local type_prefix = host_type..[[-w64-mingw32]];
		local clang_path = make_path(bin_path, type_prefix.."-clang.exe");
		local clangd_path = make_path(bin_path, "clangd.exe");
		local clangxx_path = make_path(bin_path, type_prefix.."-clang++.exe");
		local windres_path = make_path(bin_path, "windres.exe");
		local has_clang = utils:file_time(clang_path);
		if has_clang ~= nil then
			local gdb_path = make_path(bin_path, "gdb.exe");
			local gmake_path = make_path(bin_path, "make.exe");
			local ninja_path = make_path(bin_path, "ninja.exe");
			local clang_tools = {
				["type"] = "llvm-msys2";
				["target"] = host_type;
				bin = { bin_path };
				include = { make_path(bin_path, "../include") };
				lib = { make_path(bin_path, "../lib") };
				tools = {
					["clang"] = true;
					["clang++"] = (utils:file_time(clangxx_path) ~= nil);
					["gdb"] = (utils:file_time(gdb_path) ~= nil) and gdb_path or nil;
					["make"] = (utils:file_time(gmake_path) ~= nil) and gmake_path or nil;
					["ninja"] = (utils:file_time(ninja_path) ~= nil) and ninja_path or nil;
					["cc"] = clang_path..[[;]]..clang_extra_flags;
					["cxx"] = clangxx_path..[[;]]..clang_extra_flags;
					["rc"] = windres_path;
					["debugger"] = "gdb/mi";
				};
			};

			if utils:file_time(clangd_path) then
				clang_tools.tools["clangd"] = clangd_path;
				table.insert(CLANGD_PATHS, clangd_path);
			end;

			local cxx_version = utils:list_dir(make_path(bin_path, "../include/c++"));
			local clang_version = utils:list_dir(make_path(bin_path, "../lib/clang"));
			if cxx_version ~= nil and clang_version ~= nil then
				cxx_inc_path = make_path(bin_path, "../include/c++", cxx_version);
				table.insert(clang_tools.lib, make_path(bin_path, "..", type_prefix, "lib"));
				table.insert(clang_tools.include, cxx_inc_path);
				table.insert(clang_tools.include, make_path(bin_path, "../lib/clang", clang_version, "include"));

				local toolset_name = "clang-msys-"..host_type.."-"..clang_version;
				clang_tools["name"] = toolset_name;
				clang_tools["home"] = make_path(bin_path, "..");
				append_toolset(toolsets, clang_tools);
			end ;
		end;
	end;
	return toolsets;
end;

function find_llvm_msys2_tools()
	local toolsets = {};
	local mingw_roots = {};
	for i, drive in ipairs({ "C:", "D:", "E:", "F:" }) do
		for i,root in ipairs(utils:list_dir(make_path(drive, [[\msys*]]), true) or {}) do
			if root then
				table.insert(mingw_roots, make_path(drive, root,[[clang32\bin]]));
				table.insert(mingw_roots, make_path(drive, root,[[clang64\bin]]));
			end;
		end;
	end;
	for j, bin_path in ipairs(mingw_roots) do
		find_llvm_msys2_tools_in_path(bin_path, toolsets);
	end;
	return toolsets;
end;

function find_wsl_tools()
	local wsl_path = make_path(os.getenv("windir"), "system32/wsl.exe");
	if not utils:file_time(wsl_path) then
		return {};
	end;

	local wsl_reg_root = [[HKCU\Software\Microsoft\Windows\CurrentVersion\Lxss]]
	local dist_keys = utils:list_reg(wsl_reg_root);
	if dist_keys == nil then
		return {};
	end;
	local toolsets = {};
	local wsl_default_dist = utils:read_reg(wsl_reg_root.."\\DefaultDistribution");
	local wsl_dist = {};
	for i, key in ipairs(dist_keys) do
		local dist_name = utils:read_reg(wsl_reg_root.."\\"..key.."\\DistributionName");
		if dist_name then
			wsl_dist[i] = dist_name;
			base_path = utils:read_reg(wsl_reg_root.."\\"..key.."\\BasePath");
			if wsl_default_dist == key then
				wsl_default_dist = dist_name;
			end;
			local wsl_root_path = string.format([[\\wsl$\%s\]], dist_name);
			if base_path:sub(1,4) == [[\\?\]] then
				base_path = base_path:sub(5);
			end;
			local wsl_toolset = {
				["type"] = "gcc";
				["target"] = "wsl";
				["name"] = "WSL-"..dist_name;
				["home"] = base_path;
				wsl = dist_name;
				encoding = "utf-8";
				suffixes = {
					exec = "";
					shared = ".so";
					lib = ".a";
				},
				bin = {},
				include = {
					"\\"..make_path(wsl_root_path, "include");
					"\\"..make_path(wsl_root_path, "/usr/include");
					"\\"..make_path(wsl_root_path, "/usr/local/include");
				},
				lib = {},
				tools = {
					["gcc"] = true;
					["g++"] = "g++";
					["make"] = "make";
					["ninja"] = "ninja";
					["cc"] = "gcc;"..gcc_extra_flags;
					["cxx"] = "g++;"..gcc_extra_flags;
					["gdb"] = "gdb";
					["rc"] = "";
					["cmake"] = "cmake";
					["gcc-pretty-print"] = "/usr/share/gcc/python";
					["debugger"] = "gdb/mi";
					["ccache"] = "";
					["vcpkg"] = "DISABLED";
				};
				wrap_command = function(cmd, cwd)
					local prefix = "wsl -d "..dist_name.." -- bash -login -c ";
					local altered_cmd = cmd:gsub("([\"\\\\])", "\\%1");
					if cwd then
						cwd = "cd "..cwd:gsub("([& \"%{%}%(%)])", "\\%1") .. " && ";
					else
						cwd = "";
					end;
					return prefix .. "\"" .. cwd .. altered_cmd .. "\"";
				end;
				map_path = function(path)
					path = path:gsub([[^%\%\wsl%$%\]]..dist_name, "/");
					path = path:gsub("\\", "/");
					return path:gsub("^\"?(%w)(:)", function(a)
							return "/mnt/"..(a):lower();
					end);
				end;
				unmap_path = function(path)
					if path:sub(1,7) == [[\\wsl$\]] then
						return path;
					end;
					path = abs_path(path);
					if path:sub(1,4) == "\\mnt" then
						path = path:gsub("^\\mnt\\(%w)\\", function(a)
								return a:upper()..":\\";
						end);
						path = path:gsub("^\\mnt\\(%w)", function(a)
								return a:upper()..":\\";
						end);
					elseif path:sub(1,1) == "\\" then
						path = wsl_root_path..path;
					end;
					return path;
				end;
				get_pretty_print_path = function(self)
					local gcc_shared_paths = utils:list_dir(self.unmap_path("/usr/local/share/gcc-*"));
					if gcc_shared_paths then
						local gcc_version = gcc_shared_paths:sub(5);
						return "/usr/local/share/gcc-"..gcc_version.."/python";
					end;

					gcc_shared_paths = utils:list_dir(self.unmap_path("/usr/share/gcc-*"));
					if gcc_shared_paths then
						local gcc_version = gcc_shared_paths:sub(5);
						return "/usr/share/gcc-"..gcc_version.."/python";
					end;
					return self.tools["gcc-pretty-print"];
				end;
			};
			table.insert(toolsets, wsl_toolset);
		end;
	end;
	return toolsets;
end;

-- debug adapters
local function find_jlink_tools()
	local jlink_install_path = utils:read_reg([[HKLM\SOFTWARE\SEGGER\J-Link\InstallPath]]);
	if jlink_install_path == nil then
		return;
	end;

	local ver_val = utils:read_reg([[HKLM\SOFTWARE\SEGGER\J-Link\CurrentVersion]]);
	local sub_ver = ver_val%10000;
	local major_ver = math.floor(ver_val/10000);
	local patch_ver = sub_ver%100;
	sub_ver = math.floor(sub_ver/100);
	patch_ver = ("abcdefghijklmnopqrstuvwxyz"):sub(patch_ver, patch_ver);
	local ver_str = string.format("%d.%d%s", major_ver, sub_ver, patch_ver);
	local jlink_gdb_server_path = validate_path(make_path(jlink_install_path, "JLinkGDBServerCL.exe"));
	local jlink_path = validate_path(make_path(jlink_install_path, "jlink.exe"));
	if jlink_gdb_server_path == nil or jlink_path == nil then
		JLINK_INFO = nil;
		return;
	end;
	JLINK_INFO = {
		["bin"] = jlink_gdb_server_path;
		["jlink_bin"] = jlink_path;
	}
end;

local function find_pyocd(root_path, folder_name)
	if root_path == nil or PYOCD_INFO ~= nil then
		return;
	end;
	local pyocd_root = make_path(root_path, folder_name);
	local pyocd_bin = make_path(pyocd_root, "pyocd.exe");
	if utils:file_time(pyocd_bin) == nil then
		return;
	end;
	PYOCD_INFO = {
		["root"] = pyocd_root;
		["bin"] = pyocd_bin;
	};
end;

local function find_openocd(root_path, folder_name)
	if root_path == nil or OPENOCD_INFO ~= nil then
		return;
	end;
	if folder_name == nil then
		folder_name = utils:list_dir(make_path(root_path, "openocd*"));
	end;
	if folder_name == nil then
		return;
	end;
	local openocd_root = make_path(root_path, folder_name);
	local openocd_bin = make_path(openocd_root, "bin", "openocd.exe");
	local openocd_scripts = make_path(openocd_root, "share/openocd/scripts");
	if utils:file_time(openocd_scripts) == nil or utils:file_time(openocd_bin) == nil then
		return;
	end;
	OPENOCD_INFO = {
		["root"] = openocd_root;
		["bin"] = openocd_bin;
		["scripts"] = openocd_scripts;
	};
end;

local function find_t_head_debugserver()
	local thead_root = find_all_reg_val([[HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall]], "{", "InstallLocation", function(path)
			local name = utils:read_reg(make_path(path, "DisplayName"));
			if name and name:lower():match("t%-head") then
				return false;
			end;
			return true;
	end);
	thead_root = thead_root and thead_root[1];
	if not thead_root then
		return;
	end;
	local thead_debugserver = make_path(thead_root, [[bin/DebugServerConsole.exe]]);
	if utils:file_time(thead_debugserver) == nil then
		return;
	end;
	THEAD_DEBUGSERVER_INFO = {
		root = thead_root;
		bin = thead_debugserver;
	}
end;

local __debug_adapter = {
	adapters = {};
	transports = {};
	speeds = {};
	switch_adapter = function(self, adapter_name)
		local adapter = self.adapters[adapter_name];
		self.speeds = adapter.speeds;
		self.transports = adapter.transports;
	end;
	switch_transport = function(self, transport)
	end;
	make_download_command = function(self, adapter_name, transport, speed, target_output, toolset)
		local adapter = self.adapters[adapter_name];
		local cmd;
		if not transport then
			transport = "SWD";
		end;
		if not speed then
			speed = "1000";
		end;
		if adapter["type"] == "stlink" then
			cmd = "\""..adapter.prog_bin.."\" -c port="..transport.." freq="..speed.." -q -y -d \""..target_output.."\" -v -rst";
		elseif adapter["type"] == "openocd" then
			local openocd = "\""..adapter.bin.."\"";
			openocd = openocd.." --search \""..make_path(adapter.scripts).. "\"";
			openocd = openocd.." -f interface/"..adapter.interface.. ".cfg";
			local openocd_target = project:find_debug_target(toolset, adapter_name);
			if openocd_target == nil then
				print("Can't find valid target device!");
				return nil;
			end;
			transport = transport:lower();
			if adapter.interface == "stlink" then
				if transport == "swd" then
					transport = "hla_swd";
				elseif transport == "jtag" then
					transport = "hla_jtag";
				end;
			end;

			if transport then
				openocd = openocd.." -c \"transport select ".. transport .. "\"";
			end;
			if speed then
				openocd = openocd.." -c \"adapter speed "..speed:lower().."\"";
			end;
			openocd = openocd.." -f target/"..openocd_target.. ".cfg";
			openocd = openocd.." -c \"gdb_port disabled\"";
			openocd = openocd.." -c \"telnet_port disabled\"";
			openocd = openocd.." -c \"tcl_port disabled\"";
			cmd = openocd.." -c \"program \\\""..target_output:gsub("([%[%]])", "\\%1").."\\\" verify reset exit\""
		elseif adapter["type"] == "jlink" then
			local jlink = "\""..adapter.jlink_bin.."\"";
			local debug_target = project:find_debug_target(toolset, adapter_name);
			if debug_target == nil then
				print("Can't find valid target device!");
				return nil;
			end;
			transport = transport:lower();
			local jlink_flash = project:make_path(".edx/cmake", toolset.name, menu_bar.get_cmake_config(), "flash.jlink");
			write_file(
				jlink_flash,
				table.concat(
					{
						"SelectInterface "..transport;
						"Device "..debug_target;
						speed and "Speed "..speed;
						(transport == "jtag" and "JTAGConf -1 -1\nConnect") or "Connect";
						"LoadFile \""..target_output.."\"";
						"Reset";
						"Exit";
					},
					"\n"
				)
			);
			cmd = jlink.." -CommandFile \""..jlink_flash.."\"";
		elseif adapter["type"] == "pyocd" then
			local debug_target = project:find_debug_target(toolset, adapter_name);
			if debug_target == nil then
				print("Can't find valid target device!");
				return nil;
			end;
			local pyocd = "\""..adapter.bin.."\"";
			cmd = pyocd.." load -t "..debug_target.." -f "..speed.."k \""..target_output.."\"";
		end;
		return cmd;
	end;
};

function toolset_make_debug_adapter(available_adapters, extra_adapters, custom_openocd_info)
	-- ["debug_adapter_transport"] = {
	-- 	"cJTAG";
	-- 	"JTAG";
	-- 	"SWD";
	-- 	-- "FINE";
	-- };

	-- ["openocd_target"] = "cc2640r2f";
	local openocd_info = custom_openocd_info or OPENOCD_INFO;
	local debug_adapter_speed = {
		"1000";
		"2000";
		"4000";
		"8000";
		"15000";
		"20000";
		"50000";
		"500";
		"300";
		"100";
	};
	local all_adapters = {};
	if JLINK_INFO then
		all_adapters["JLink"] = table.clone(
			{
				["type"] = "jlink";
				["transports"] = {
					"SWD";
					"JTAG";
					"cJTAG";
					"C2";
					"FINE";
					"ICSP";
				};
				["speeds"] = table.iclone({"auto", "adaptive"}, debug_adapter_speed);
			},
			JLINK_INFO
		);
	end;
	if openocd_info then
		all_adapters["JLink(OpenOCD)"] = table.clone(
			{
				["type"] = "openocd";
				["interface"] = "jlink";
				["transports"] = {
					"SWD";
					"JTAG";
				};
				["speeds"] = debug_adapter_speed;
			},
			openocd_info
		);
		all_adapters["CMSIS-DAP"] = table.clone(
			{
				["type"] = "openocd";
				["interface"] = "cmsis-dap";
				["transports"] = {
					"SWD";
					"JTAG";
				};
				["speeds"] = debug_adapter_speed;
			},
			openocd_info
		);
		all_adapters["ST-Link(OpenOCD)"] = table.clone(
			{
				["type"] = "openocd";
				["interface"] = "stlink";
				["transports"] = {
					"SWD";
					"JTAG";
				};
				["speeds"] = debug_adapter_speed;
			},
			openocd_info
		);
	end;
	if STLINK_INFO then
		all_adapters["ST-Link"] = table.clone(
			{
				["type"] = "stlink";
				["transports"] = {
					"SWD";
					"JTAG";
				};
				["speeds"] = debug_adapter_speed;
			},
			STLINK_INFO
		);
	end;
	if PYOCD_INFO then
		all_adapters["pyOCD"] = table.clone(
			{
				["type"] = "pyocd";
				["transports"] = {
					"SWD";
					"JTAG";
				};
				["speeds"] = debug_adapter_speed;
			},
			PYOCD_INFO
		);
	end;
	if THEAD_DEBUGSERVER_INFO then
		all_adapters["T-Head DebugServer"] = table.clone(
			{
				["type"] = "t-head";
				["transports"] = {};
				["speeds"] = {
					"1000",
					"2000",
					"4000",
					"8000",
					"12000",
					"24000",
				};
			},
			THEAD_DEBUGSERVER_INFO
		);
	end;
	local adapters = {};
	for idx,key in ipairs(available_adapters) do
		adapters[key] = all_adapters[key];
	end;
	for key, info in pairs((extra_adapters and table.clone(extra_adapters)) or {}) do
		if adapters[key] then
			-- merge adapter info
			adapters[key] = table.clone(adapters[key], info);
		else
			adapters[key] = info;
		end;
	end;
	local adapter_obj = table.clone(__debug_adapter, {["adapters"] = adapters});
	return adapter_obj;
end;

local function find_arm_eabi_toolsets(root_path, folder_name)
	local tools = (folder_name and {folder_name}) or utils:list_dir(make_path(root_path, "*"), true);
	local compilers = {};
	if tools == nil then
		return nil;
	end;
	for i, name in ipairs(tools) do
		if name:match([[^gcc%-arm%-none%-eabi[%-0-9_%.]+]]) or
			name:match([[^arm%-gnu%-toolchain%-.*%-arm%-none%-eabi]])
		then
			-- GCC arm-none-eabi
			local gcc_root = make_path(root_path, name);
			local gcc_bin_root = make_path(gcc_root, "bin");
			local gcc_version = utils:list_dir(make_path(gcc_root, [[lib/gcc/arm-none-eabi/*]]));
			local gcc_bin = validate_path(make_path(gcc_bin_root, [[arm-none-eabi-gcc.exe]]));
			local gxx_bin = validate_path(make_path(gcc_bin_root, [[arm-none-eabi-g++.exe]]));
			if gcc_bin then
				local compiler = {
					["type"] = "gcc-arm-none-eabi";
					version = "gcc-arm-none-eabi-"..gcc_version;
					root = gcc_root;
					bin = {
						gcc_bin_root
					};
					include = {
						make_path(gcc_root, [[arm-none-eabi/include]]);
						make_path(gcc_root, [[arm-none-eabi/include/c++]], gcc_version, [[arm-none-eabi]]);
						make_path(gcc_root, [[arm-none-eabi/include/c++]], gcc_version);
						make_path(gcc_root, [[lib/gcc/arm-none-eabi]], gcc_version, [[include]]);
					};
					tools = {
						["gcc"] = true;
						["g++"] = true;
						["cc"] = (gcc_bin and (gcc_bin..";"..gcc_extra_flags.." -static")) or nil;
						["cxx"] = (gxx_bin and (gxx_bin..";"..gcc_extra_flags.." -static")) or nil;
						["gdb"] = validate_path(make_path(gcc_bin_root, [[arm-none-eabi-gdb.exe]]));
						["debugger"] = "openocd";
						["vcpkg"] = "DISABLED";
						["gcc-pretty-print"] = validate_path(make_path(gcc_root, [[share/gcc-]]..gcc_version, [[python]]));
					};
				};
				compilers[compiler.version] = compiler;
				ARM_NONE_EABI_TOOLSETS[compiler.version] = compiler;
			end;
		elseif name:match([[^LLVMEmbeddedToolchainForArm%-.*]]) or name:match([[LLVM%-ET%-Arm%-.*]]) then
			-- LLVMEmbeddedToolchainForArm
			local clang_root = make_path(root_path, name);
			local clang_bin_root = make_path(clang_root, "bin");
			local clang_version = utils:list_dir(make_path(clang_root, [[lib/clang/*]]));
			local clang_bin = validate_path(make_path(clang_bin_root, [[clang.exe]]));
			local clangxx_bin = validate_path(make_path(clang_bin_root, [[clang++.exe]]));
			if clang_bin then
				local compiler = {
					["type"] = "clang-arm-none-eabi";
					version = "clang-arm-none-eabi-"..clang_version;
					root = clang_bin_root;
					bin = {
						clang_bin_root
					};
					include = {
						make_path(clang_root, [[lib/clang]], clang_version, [[include]]);
						make_path(clang_root, [[include/c]]);
						make_path(clang_root, [[include/c++/v1]]);
					};
					tools = {
						["clang"] = true;
						["clang++"] = true;
						["cc"] = (clang_bin and (clang_bin..";"..clang_extra_flags)) or nil;
						["cxx"] = (clangxx_bin and (clangxx_bin..";"..clang_extra_flags)) or nil;
						["debugger"] = "openocd";
						["vcpkg"] = "DISABLED";
						["newlib"] = validate_path(make_path(clang_bin_root, "newlib.cfg"));
					};
				};
				compilers[compiler.version] = compiler;
				ARM_NONE_EABI_TOOLSETS[compiler.version] = compiler;
			end;
		end;
	end;
	return compilers;
end;

local function find_armclang_toolsets()
	local compilers = {};
	local armcc_vers = {};
	local vals = find_all_reg_val("HKLM\\SOFTWARE\\ARM\\", "ArmCompilerforEmbedded", "INSTALLDIR", function(path)
			local name = path:sub(19);
			local ver = name:gsub("^.*%-(%d+%.%d+)$", "%1");
			if name and ver then
				table.insert(armcc_vers, {
						["name"] = name;
						["ver"] = ver;
				});
			end;
			return false;
	end);

	for idx, info in ipairs(armcc_vers) do
		local armclang_root = make_path(vals[idx]);
		local armclang_bin_root = make_path(armclang_root, "bin");
		local armclang_version = utils:list_dir(make_path(armclang_root, [[lib/clang/*]]));
		local armclang_bin = validate_path(make_path(armclang_bin_root, [[armclang.exe]]));
		if armclang_bin then
			local compiler = {
				["type"] = "armclang";
				version = "armclang-"..info.ver;
				root = armclang_bin_root;
				bin = {
					armclang_bin_root
				};
				include = {
					make_path(armclang_root, [[lib/clang]], armclang_version, [[include]]);
					make_path(armclang_root, [[include]]);
					make_path(armclang_root, [[include/libcxx]]);
				};
				tools = {
					["clang"] = true;
					["clang++"] = true;
					["cc"] = (armclang_bin and (armclang_bin..";"..clang_extra_flags)) or nil;
					["cxx"] = (armclang_bin and (armclang_bin..";"..clang_extra_flags)) or nil;
					["debugger"] = "openocd";
					["vcpkg"] = "DISABLED";
				};
			};
			compilers[compiler.version] = compiler;
			ARM_NONE_EABI_TOOLSETS[compiler.version] = compiler;
		end;
	end;

	return compilers;
end;

-- stm32cube sdks
local function collect_stm32cube_sdks(root_path)
	local stm32cube_repo_root = make_path(root_path, "STM32Cube/Repository");
	if not utils:file_time(stm32cube_repo_root) then
		return;
	end;
	local sdks = {};
	local default_sdk = nil;

	for idx, name in ipairs(utils:list_dir(make_path(stm32cube_repo_root, [[stm32cube_fw_*]]), true) or {}) do
		local sdk_root = make_path(stm32cube_repo_root, name);
		if utils:is_dir(sdk_root) then
			local sdk_version = name:lower():match("^stm32cube_fw_.+_v([%.0-9]+)");
			local cpus = {};
			local hal_driver = utils:list_dir(make_path(sdk_root, "Drivers/STM32*"));
			local cmsis_device = utils:list_dir(make_path(sdk_root, "Drivers/CMSIS/Device/ST/*"));
			local target_type = hal_driver:match("^STM32(.-)_HAL_Driver");
			local hal_driver_path = make_path(sdk_root, "Drivers", hal_driver);
			local cpu_specified_hdrs = utils:list_dir(make_path(sdk_root, "Drivers/CMSIS/Device/ST", cmsis_device, "Include/*.h"), true);
			for idx, name in ipairs(cpu_specified_hdrs) do
				local name = name:match("^(stm32.+)%.h");
				table.insert(cpus, name);
			end;

			local sdk_target_name = "stm32"..target_type:lower();
			local sdk_version_info = sdk_target_name.."-"..sdk_version;
			local sdk = {
				home = sdk_root;
				version = sdk_version_info;
				target = sdk_target_name;
				include = {};
				lib = {};
				exts = {
					["hal_driver_src"] = validate_path(make_path(hal_driver_path, "Src"));
					["rtos_src"] = validate_path(make_path(sdk_root, "Drivers/CMSIS/RTOS"));
					["rtos2_src"] = validate_path(make_path(sdk_root, "Drivers/CMSIS/RTOS2/Source"));
					["cmsis_devices_st_src"] = validate_path(make_path(sdk_root, "Drivers/CMSIS/Device/ST", cmsis_device, "Source"));
					["cmsis_dsp_src"] = validate_path(make_path(sdk_root, "Drivers/CMSIS/DSP/Source"));
					["cmsis_dap_src"] = validate_path(make_path(sdk_root, "Drivers/CMSIS/DAP/Firmware/Source"));
					["cmsis_dap_firmware_root"] = validate_path(make_path(sdk_root, "Drivers/CMSIS/DAP/Firmware"));
				};
				["cpus"] = cpus;
			};

			append_valid_path(sdk.include, make_path(hal_driver_path, "Inc"));
			append_valid_path(sdk.include, make_path(sdk_root, "Drivers/CMSIS/Device/ST", cmsis_device, "Include"));
			append_valid_path(sdk.include, make_path(sdk_root, "Drivers/CMSIS/DAP/Firmware/Include"));
			append_valid_path(sdk.include, make_path(sdk_root, "Drivers/CMSIS/DSP/Include"));
			append_valid_path(sdk.include, make_path(sdk_root, "Drivers/CMSIS/Core/Include"));
			-- append_valid_path(sdk.include, make_path(sdk_root, "Drivers/CMSIS/CoreA/Include"));
			append_valid_path(sdk.include, make_path(sdk_root, "Drivers/CMSIS/Include"));

			append_valid_path(sdk.lib, make_path(sdk_root, "Drivers/CMSIS/Lib/GCC"));
			append_valid_path(sdk.lib, make_path(sdk_root, "Drivers/CMSIS/DSP/Lib/GCC"));
			sdks[sdk.version] = sdk;
			if default_sdk == nil then
				default_sdk = sdk.version;
			end;
		end;
	end;
	return sdks, default_sdk, stm32cube_repo_root, "STM32Cube";
end;

local function collect_stm32cube_sdks_edx(root_path)
	local stm32cube_repo_root = make_path(root_path, "stm32cube");
	if not utils:file_time(stm32cube_repo_root) then
		return;
	end;
	local targets = utils:list_dir(make_path(stm32cube_repo_root, [[Drivers\CMSIS\Device\ST\STM32*]]), true);
	if not targets or #targets == 0 then
		return;
	end;
	local sdks = {};
	local default_sdk = nil;
	local sdk_root = stm32cube_repo_root
	
	for idx, cmsis_device in ipairs(targets) do
		local cpus = {};
		local target_type = cmsis_device;
		local hal_driver_path = make_path(sdk_root, "Drivers", cmsis_device.."_HAL_Driver");
		local cpu_specified_hdrs = utils:list_dir(make_path(sdk_root, "Drivers/CMSIS/Device/ST", cmsis_device, "Include/*.h"), true);
		for idx, name in ipairs(cpu_specified_hdrs) do
			local name = name:match("^(stm32.+)%.h");
			table.insert(cpus, name);
		end;


		local sdk_target_name = target_type:lower();
		local sdk_version_info = sdk_target_name;
		local sdk = {
			home = sdk_root;
			version = sdk_version_info;
			target = sdk_target_name;
			include = {};
			lib = {};
			exts = {
				["hal_driver_src"] = validate_path(make_path(hal_driver_path, "Src"));
				["rtos_src"] = validate_path(make_path(sdk_root, "Drivers/CMSIS/RTOS"));
				["rtos2_src"] = validate_path(make_path(sdk_root, "Drivers/CMSIS/RTOS2/Source"));
				["cmsis_devices_st_src"] = validate_path(make_path(sdk_root, "Drivers/CMSIS/Device/ST", cmsis_device, "Source"));
				["cmsis_dsp_src"] = validate_path(make_path(sdk_root, "Drivers/CMSIS/DSP/Source"));
				["cmsis_dap_src"] = validate_path(make_path(sdk_root, "Drivers/CMSIS/DAP/Firmware/Source"));
				["cmsis_dap_firmware_root"] = validate_path(make_path(sdk_root, "Drivers/CMSIS/DAP/Firmware"));
			};
			["cpus"] = cpus;
		};

		append_valid_path(sdk.include, make_path(hal_driver_path, "Inc"));
		append_valid_path(sdk.include, make_path(sdk_root, "Drivers/CMSIS/Device/ST", cmsis_device, "Include"));
		append_valid_path(sdk.include, make_path(sdk_root, "Drivers/CMSIS/DAP/Firmware/Include"));
		append_valid_path(sdk.include, make_path(sdk_root, "Drivers/CMSIS/DSP/Include"));
		append_valid_path(sdk.include, make_path(sdk_root, "Drivers/CMSIS/Core/Include"));
		-- append_valid_path(sdk.include, make_path(sdk_root, "Drivers/CMSIS/CoreA/Include"));
		append_valid_path(sdk.include, make_path(sdk_root, "Drivers/CMSIS/Include"));

		append_valid_path(sdk.lib, make_path(sdk_root, "Drivers/CMSIS/Lib/GCC"));
		append_valid_path(sdk.lib, make_path(sdk_root, "Drivers/CMSIS/DSP/Lib/GCC"));
		sdks[sdk.version] = sdk;
		if default_sdk == nil then
			default_sdk = sdk.version;
		end;
	end;
	return sdks, default_sdk, stm32cube_repo_root, "STM32Cube";
end;

local function find_stm32cube_sdks(root_path, collect_func)
	if collect_func == nil then
		collect_func = collect_stm32cube_sdks;
	end;
	local sdks, default_sdk, stm32cube_repo_root, sdk_name = collect_func(root_path);
	if sdks == nil then
		return;
	end;
	local compilers = ARM_NONE_EABI_TOOLSETS;
	local compiler_names = table.keys(compilers);
	table.sort(compiler_names);
	local default_ver = compiler_names[1];
	if default_sdk == nil or default_ver == nil then
		return;
	end;
	local debug_adapter = toolset_make_debug_adapter({"JLink", "JLink(OpenOCD)", "CMSIS-DAP", "ST-Link", "ST-Link(OpenOCD)", "pyOCD"});
	local toolset = {
		["type"] = "STM32Cube";
		["target"] = "arm-none-eabi";
		["name"] = sdk_name;
		["home"] = stm32cube_repo_root;
		encoding = "utf-8";
		suffixes = {
			exec = ".elf";
			shared = ".so";
			lib = ".a";
		},
		["sdks"] = table.keys(sdks);
		["sdks_info"] = sdks;
		["vers"] = compiler_names;
		["vers_info"] = compilers;
		["version"] = "";
		["sdk"] = "";
		["switch_version"] = function(self, compiler_ver, sdk_ver)
			local compiler = compilers[compiler_ver];
			local sdk = sdks[sdk_ver];
			self.bin = table.iclone({}, compiler.bin or {}, sdk.bin or {});
			self.include = table.iclone({}, (sdk.include or {}), (compiler.include or {}));
			self.lib = table.iclone({}, (sdk.lib or {}), (compiler.lib or {}));
			self.tools = table.clone(compiler.tools);
			self.version = compiler_ver;
			self.sdk = sdk_ver;
			self.envs = {
				["SDK_TYPE"] = self["type"];
				["SDK_HOME"] = sdk.home;
				["SDK_INCLUDE"] = table.concat(self.include, ";");
				["SDK_LIB"] = table.concat(self.lib, ";");
				["SDK_HAL_DRIVER_SRC"] = sdk.exts["hal_driver_src"];
				["SDK_CMSIS_DEVICES_ST_SRC"] = sdk.exts["cmsis_devices_st_src"];
				["SDK_RTOS_SRC"] = sdk.exts["rtos_src"];
				["SDK_RTOS2_SRC"] = sdk.exts["rtos2_src"];
			};
			if JLINK_INFO and JLINK_INFO.bin then
				local segger_rtt_src_path = make_path(JLINK_INFO.bin, "../Samples/RTT");
				local file_name = utils:list_dir(segger_rtt_src_path.."/SEGGER_RTT_*.zip");
				if file_name then
					self.envs["SEGGER_RTT_SRC"] = make_path(segger_rtt_src_path, file_name);
				end;
			end;
			return self;
		end;
		["debug_adapter"] = debug_adapter;
		["find_svd"] = function(self, device)
			local svd_root = make_path(self.home, "svd");
			local main_name = device:sub(1, 9);
			local common_name = main_name:sub(1, 7).."x"..main_name:sub(9, 9);
			local svd_paths = {
				make_path(svd_root, device..".svd"),
				make_path(svd_root, main_name..".svd"),
				make_path(svd_root, common_name..".svd"),
				make_path(svd_root, device:sub(1, 10)..".svd"),
				make_path(svd_root, device:sub(1, 11)..".svd"),
				make_path(svd_root, device:sub(1, 12)..".svd"),
			};
			for idx, path in ipairs(svd_paths) do
				if utils:file_time(path) then
					return path;
				end;
			end;
			return nil;
		end;
	};
	table.sort(toolset.sdks);
	toolset:switch_version(default_ver, default_sdk);
	return {toolset};
end;

function stm32cube_find_toolset_for_device(device_name)
	local proj_type_prefix = device_name:sub(1,7);
	local sdk_name = "STM32Cube";
	local sdk_version = nil;
	local toolset_version = nil;
	for idx, toolset in ipairs(TOOLSETS) do
		if toolset["type"] == "STM32Cube" then
			for ver, sdk in pairs(toolset["sdks_info"]) do
				local sdk_target_prefix = sdk.target:sub(1,7);
				if sdk_target_prefix == proj_type_prefix then
					-- found the sdk
					sdk_version = ver;
					break;
				end;
			end;
			if sdk_version then
				local toolset_names = table.keys(toolset["vers_info"]);
				table.sort(toolset_names);
				toolset_version = toolset_names[#toolset_names];
				sdk_name = toolset["name"];
				break;
			end;
		end;
	end;
	if sdk_version == nil or toolset_version == nil then
		return;
	end;
	return sdk_name.."|"..toolset_version.."|"..sdk_version;
end;

-- fm33 sdks
local function collect_fm33_sdks(root_path)
	local fm33_repo_root = make_path(root_path, "fm33");
	if not utils:file_time(fm33_repo_root) then
		return;
	end;
	local targets = utils:list_dir(make_path(fm33_repo_root, [[Drivers\FM33*_FL_Driver]]), true);
	if not targets or #targets == 0 then
		return;
	end;
	local sdks = {};
	local default_sdk = nil;
	local sdk_root = fm33_repo_root
	
	for idx, cmsis_target_driver in ipairs(targets) do
		local cpus = {};
		local target_type = cmsis_target_driver:lower():match("(fm33.*)_fl_driver");
		local driver_path = make_path(sdk_root, "Drivers", cmsis_target_driver);
		table.insert(cpus, target_type);

		local sdk_target_name = target_type:lower();
		local sdk_version_info = sdk_target_name;
		local sdk = {
			home = sdk_root;
			version = sdk_version_info;
			target = sdk_target_name;
			include = {};
			lib = {};
			exts = {
				["fl_driver_src"] = validate_path(make_path(driver_path, "Src"));
				["cmsis_devices_fm_src"] = validate_path(make_path(sdk_root, "Drivers/CMSIS/Device/FM/FM33xx/Source"));
			};
			["cpus"] = cpus;
		};

		append_valid_path(sdk.include, make_path(driver_path, "Inc"));
		append_valid_path(sdk.include, make_path(sdk_root, "Drivers/CMSIS/Device/FM/FM33xx/Include"));
		append_valid_path(sdk.include, make_path(sdk_root, "Drivers/CMSIS/Include"));

		sdks[sdk.version] = sdk;
		if default_sdk == nil then
			default_sdk = sdk.version;
		end;
	end;
	return sdks, default_sdk, fm33_repo_root, "FM33";
end;

local function find_fm33_sdks(root_path, collect_func)
	if collect_func == nil then
		collect_func = collect_fm33_sdks;
	end;
	local sdks, default_sdk, fm33_repo_root, sdk_name = collect_func(root_path);
	if sdks == nil then
		return;
	end;
	local compilers = ARM_NONE_EABI_TOOLSETS;
	local compiler_names = table.keys(compilers);
	table.sort(compiler_names);
	local default_ver = compiler_names[1];
	if default_sdk == nil or default_ver == nil then
		return;
	end;
	local debug_adapter = toolset_make_debug_adapter({"JLink", "JLink(OpenOCD)", "CMSIS-DAP", "ST-Link", "ST-Link(OpenOCD)", "pyOCD"});
	local toolset = {
		["type"] = "FM33";
		["target"] = "arm-none-eabi";
		["name"] = sdk_name;
		["home"] = fm33_repo_root;
		encoding = "utf-8";
		suffixes = {
			exec = ".elf";
			shared = ".so";
			lib = ".a";
		},
		["sdks"] = table.keys(sdks);
		["sdks_info"] = sdks;
		["vers"] = compiler_names;
		["vers_info"] = compilers;
		["version"] = "";
		["sdk"] = "";
		["switch_version"] = function(self, compiler_ver, sdk_ver)
			local compiler = compilers[compiler_ver];
			local sdk = sdks[sdk_ver];
			self.bin = table.iclone({}, compiler.bin or {}, sdk.bin or {});
			self.include = table.iclone({}, (sdk.include or {}), (compiler.include or {}));
			self.lib = table.iclone({}, (sdk.lib or {}), (compiler.lib or {}));
			self.tools = table.clone(compiler.tools);
			self.version = compiler_ver;
			self.sdk = sdk_ver;
			self.envs = {
				["SDK_TYPE"] = self["type"];
				["SDK_HOME"] = sdk.home;
				["SDK_INCLUDE"] = table.concat(self.include, ";");
				["SDK_LIB"] = table.concat(self.lib, ";");
				["SDK_FL_DRIVER_SRC"] = sdk.exts["fl_driver_src"];
				["SDK_CMSIS_DEVICES_FM_SRC"] = sdk.exts["cmsis_devices_fm_src"];
			};
			if JLINK_INFO and JLINK_INFO.bin then
				local segger_rtt_src_path = make_path(JLINK_INFO.bin, "../Samples/RTT");
				local file_name = utils:list_dir(segger_rtt_src_path.."/SEGGER_RTT_*.zip");
				if file_name then
					self.envs["SEGGER_RTT_SRC"] = make_path(segger_rtt_src_path, file_name);
				end;
			end;
			return self;
		end;
		["debug_adapter"] = debug_adapter;
		["find_svd"] = function(self, device)
			local svd_root = make_path(self.home, "svd");
			local main_name = device:sub(1, 9);
			local common_name = main_name:sub(1, 7).."x"..main_name:sub(9, 9);
			local svd_paths = {
				make_path(svd_root, device..".svd"),
				make_path(svd_root, main_name..".svd"),
				make_path(svd_root, common_name..".svd"),
				make_path(svd_root, device:sub(1, 10)..".svd"),
				make_path(svd_root, device:sub(1, 11)..".svd"),
				make_path(svd_root, device:sub(1, 12)..".svd"),
			};
			for idx, path in ipairs(svd_paths) do
				if utils:file_time(path) then
					return path;
				end;
			end;
			return nil;
		end;
	};
	table.sort(toolset.sdks);
	toolset:switch_version(default_ver, default_sdk);
	return {toolset};
end;

function fm33_find_toolset_for_device(device_name)
	local proj_type_prefix = device_name:sub(1,7);
	local sdk_name = "FM33";
	local sdk_version = nil;
	local toolset_version = nil;
	for idx, toolset in ipairs(TOOLSETS) do
		if toolset["type"] == "FM33" then
			for ver, sdk in pairs(toolset["sdks_info"]) do
				local sdk_target_prefix = sdk.target:sub(1,7);
				if sdk_target_prefix == proj_type_prefix then
					-- found the sdk
					sdk_version = ver;
					break;
				end;
			end;
			if sdk_version then
				local toolset_names = table.keys(toolset["vers_info"]);
				table.sort(toolset_names);
				toolset_version = toolset_names[#toolset_names];
				sdk_name = toolset["name"];
				break;
			end;
		end;
	end;
	if sdk_version == nil or toolset_version == nil then
		return;
	end;
	return sdk_name.."|"..toolset_version.."|"..sdk_version;
end;

-- air001/air32 sdks
local function collect_airmcu_sdks(root_path)
	local airmcu_repo_root = make_path(root_path, "AirMCU");
	if not utils:file_time(airmcu_repo_root) then
		return;
	end;
	local targets = utils:list_dir(make_path(airmcu_repo_root, [[CMSIS\Device\AIR*]]), true);
	if not targets or #targets == 0 then
		return;
	end;
	local sdks = {};
	local default_sdk = nil;
	local sdk_root = airmcu_repo_root

	for idx, cmsis_device in ipairs(targets) do
		local cpus = {};
		local target_type = cmsis_device;
		local hal_driver_path = make_path(sdk_root, cmsis_device.."_HAL_Driver");
		local cpu_specified_hdrs = utils:list_dir(make_path(sdk_root, "CMSIS/Device", cmsis_device, "Include/*.h"), true);
		for idx, name in ipairs(cpu_specified_hdrs) do
			local target_name = name:match("^(air%d%d%d[a-zA-Z]+)%.h");
			if target_name == nil then
				target_name = name:match("^(air%d%df%d%d[0-9a-zA-Z]+)%.h");
			end;
			if target_name then
				table.insert(cpus, target_name);
			end;
		end;


		local sdk_target_name = target_type:lower();
		local sdk_version_info = sdk_target_name;
		local sdk = {
			home = sdk_root;
			version = sdk_version_info;
			target = sdk_target_name;
			include = {};
			lib = {};
			exts = {
				["hal_driver_src"] = validate_path(make_path(hal_driver_path, "Src"));
				["rtos_src"] = validate_path(make_path(sdk_root, "CMSIS/RTOS"));
				["rtos2_src"] = validate_path(make_path(sdk_root, "CMSIS/RTOS2/Source"));
				["cmsis_devices_src"] = validate_path(make_path(sdk_root, "CMSIS/Device", cmsis_device, "Source"));
				["cmsis_dsp_src"] = validate_path(make_path(sdk_root, "CMSIS/DSP/Source"));
				["cmsis_dap_src"] = validate_path(make_path(sdk_root, "CMSIS/DAP/Firmware/Source"));
				["cmsis_dap_firmware_root"] = validate_path(make_path(sdk_root, "CMSIS/DAP/Firmware"));
			};
			["cpus"] = cpus;
		};

		append_valid_path(sdk.include, make_path(hal_driver_path, "Inc"));
		append_valid_path(sdk.include, make_path(sdk_root, "CMSIS/Device", cmsis_device, "Include"));
		append_valid_path(sdk.include, make_path(sdk_root, "CMSIS/DAP/Firmware/Include"));
		append_valid_path(sdk.include, make_path(sdk_root, "CMSIS/DSP/Include"));
		append_valid_path(sdk.include, make_path(sdk_root, "CMSIS/Core/Include"));
		-- append_valid_path(sdk.include, make_path(sdk_root, "Drivers/CMSIS/CoreA/Include"));
		append_valid_path(sdk.include, make_path(sdk_root, "CMSIS/Include"));

		append_valid_path(sdk.lib, make_path(sdk_root, "CMSIS/Lib/GCC"));
		append_valid_path(sdk.lib, make_path(sdk_root, "CMSIS/DSP/Lib/GCC"));
		sdks[sdk.version] = sdk;
		if default_sdk == nil then
			default_sdk = sdk.version;
		end;
	end;
	return sdks, default_sdk, airmcu_repo_root, "AirMCU";
end;

local function find_airmcu_sdks(root_path, collect_func)
	if collect_func == nil then
		collect_func = collect_airmcu_sdks;
	end;
	local sdks, default_sdk, airmcu_repo_root, sdk_name = collect_func(root_path);
	if sdks == nil then
		return;
	end;
	local compilers = ARM_NONE_EABI_TOOLSETS;
	local compiler_names = table.keys(compilers);
	table.sort(compiler_names);
	local default_ver = compiler_names[1];
	if default_sdk == nil or default_ver == nil then
		return;
	end;
	local debug_adapter = toolset_make_debug_adapter({"JLink", "JLink(OpenOCD)", "CMSIS-DAP", "ST-Link(OpenOCD)", "pyOCD"});
	local toolset = {
		["type"] = "AirMCU";
		["target"] = "arm-none-eabi";
		["name"] = sdk_name;
		["home"] = airmcu_repo_root;
		encoding = "utf-8";
		suffixes = {
			exec = ".elf";
			shared = ".so";
			lib = ".a";
		},
		["sdks"] = table.keys(sdks);
		["sdks_info"] = sdks;
		["vers"] = compiler_names;
		["vers_info"] = compilers;
		["version"] = "";
		["sdk"] = "";
		["switch_version"] = function(self, compiler_ver, sdk_ver)
			local compiler = compilers[compiler_ver];
			local sdk = sdks[sdk_ver];
			self.bin = table.iclone({}, compiler.bin or {}, sdk.bin or {});
			self.include = table.iclone({}, (sdk.include or {}), (compiler.include or {}));
			self.lib = table.iclone({}, (sdk.lib or {}), (compiler.lib or {}));
			self.tools = table.clone(compiler.tools);
			self.version = compiler_ver;
			self.sdk = sdk_ver;
			self.envs = {
				["SDK_TYPE"] = self["type"];
				["SDK_HOME"] = sdk.home;
				["SDK_INCLUDE"] = table.concat(self.include, ";");
				["SDK_LIB"] = table.concat(self.lib, ";");
				["SDK_HAL_DRIVER_SRC"] = sdk.exts["hal_driver_src"];
				["SDK_CMSIS_DEVICES_SRC"] = sdk.exts["cmsis_devices_src"];
				["SDK_RTOS_SRC"] = sdk.exts["rtos_src"];
				["SDK_RTOS2_SRC"] = sdk.exts["rtos2_src"];
			};
			if JLINK_INFO and JLINK_INFO.bin then
				local segger_rtt_src_path = make_path(JLINK_INFO.bin, "../Samples/RTT");
				local file_name = utils:list_dir(segger_rtt_src_path.."/SEGGER_RTT_*.zip");
				if file_name then
					self.envs["SEGGER_RTT_SRC"] = make_path(segger_rtt_src_path, file_name);
				end;
			end;
			return self;
		end;
		["debug_adapter"] = debug_adapter;
	};
	table.sort(toolset.sdks);
	toolset:switch_version(default_ver, default_sdk);
	return {toolset};
end;

function airmcu_find_toolset_for_device(device_name)
	local extract_prefix;
	if device_name:sub(1, 6) == "air32f" then
		extract_prefix = function(name) return name:sub(1,7); end;
	else
		extract_prefix = function(name) return name:sub(1,6); end;
	end;
	local proj_type_prefix = extract_prefix(device_name);
	local sdk_name = "AirMCU";
	local sdk_version = nil;
	local toolset_version = nil;
	for idx, toolset in ipairs(TOOLSETS) do
		if toolset["type"] == "AirMCU" then
			for ver, sdk in pairs(toolset["sdks_info"]) do
				local sdk_target_prefix = extract_prefix(sdk.target);
				if sdk_target_prefix == proj_type_prefix then
					-- found the sdk
					sdk_version = ver;
					break;
				end;
			end;
			if sdk_version then
				local toolset_names = table.keys(toolset["vers_info"]);
				table.sort(toolset_names);
				toolset_version = toolset_names[#toolset_names];
				sdk_name = toolset["name"];
				break;
			end;
		end;
	end;
	if sdk_version == nil or toolset_version == nil then
		return;
	end;
	return sdk_name.."|"..toolset_version.."|"..sdk_version;
end;

-- XHSC sdks
local function collect_xhsc_sdks(root_path)
	local xhsc_repo_root = make_path(root_path, "XHSC");
	if not utils:file_time(xhsc_repo_root) then
		return;
	end;
	local sdks = {};
	local default_sdk = nil;

	for idx, name in ipairs(utils:list_dir(make_path(xhsc_repo_root, [[hc32*_ddl_rev*]]), true) or {}) do
		local sdk_root = make_path(xhsc_repo_root, name);
		if utils:is_dir(sdk_root) then
			local target_type, sdk_version = name:lower():match("^hc32([0-9a-z]+)_ddl_rev([%.0-9]+)");
			local cpus = {};
			local cmsis_device = utils:list_dir(make_path(sdk_root, "drivers/CMSIS/Device/HDSC/*"));
			local driver_path = make_path(sdk_root, "drivers/hc32_ll_driver");
			local cpu_specified_hdrs = utils:list_dir(make_path(sdk_root, "drivers/CMSIS/Device/HDSC", cmsis_device, "Include/*.h"), true);
			for idx, name in ipairs(cpu_specified_hdrs) do
				local name = name:match("^(hc32.+)%.h");
				table.insert(cpus, name);
			end;
			
			local sdk_target_name = "hc32"..target_type:lower();
			local sdk_version_info = sdk_target_name.."-"..sdk_version;
			local sdk = {
				home = sdk_root;
				version = sdk_version_info;
				target = sdk_target_name;
				include = {};
				lib = {};
				exts = {
					["driver_src"] = validate_path(make_path(driver_path, "src"));
					["cmsis_src"] = validate_path(make_path(sdk_root, "drivers/CMSIS/Device/HDSC", cmsis_device, "Source"));
					["usb_src"] = validate_path(make_path(sdk_root, "midwares/hc32/usb"));
				};
				["cpus"] = cpus;
			};

			append_valid_path(sdk.include, make_path(driver_path, "inc"));
			append_valid_path(sdk.include, make_path(sdk_root, "drivers/CMSIS/Device/HDSC", cmsis_device, "Include"));
			append_valid_path(sdk.include, make_path(sdk_root, "drivers/CMSIS/Include"));

			-- append_valid_path(sdk.lib, make_path(sdk_root, "Drivers/CMSIS/Lib/GCC"));
			sdks[sdk.version] = sdk;
			if default_sdk == nil then
				default_sdk = sdk.version;
			end;
		end;
	end;
	return sdks, default_sdk, xhsc_repo_root, "XHSC";
end;

local function find_xhsc_sdks(root_path, collect_func)
	if collect_func == nil then
		collect_func = collect_xhsc_sdks;
	end;
	local sdks, default_sdk, xhsc_repo_root, sdk_name = collect_func(root_path);
	if sdks == nil then
		return;
	end;
	local compilers = ARM_NONE_EABI_TOOLSETS;
	local compiler_names = table.keys(compilers);
	table.sort(compiler_names);
	local default_ver = compiler_names[1];
	if default_sdk == nil or default_ver == nil then
		return;
	end;
	local debug_adapter = toolset_make_debug_adapter({"JLink", "pyOCD"});
	local toolset = {
		["type"] = "XHSC";
		["target"] = "arm-none-eabi";
		["name"] = sdk_name;
		["home"] = xhsc_repo_root;
		encoding = "utf-8";
		suffixes = {
			exec = ".elf";
			shared = ".so";
			lib = ".a";
		},
		["sdks"] = table.keys(sdks);
		["sdks_info"] = sdks;
		["vers"] = compiler_names;
		["vers_info"] = compilers;
		["version"] = "";
		["sdk"] = "";
		["switch_version"] = function(self, compiler_ver, sdk_ver)
			local compiler = compilers[compiler_ver];
			local sdk = sdks[sdk_ver];
			self.bin = table.iclone({}, compiler.bin or {}, sdk.bin or {});
			self.include = table.iclone({}, (sdk.include or {}), (compiler.include or {}));
			self.lib = table.iclone({}, (sdk.lib or {}), (compiler.lib or {}));
			self.tools = table.clone(compiler.tools);
			self.version = compiler_ver;
			self.sdk = sdk_ver;
			self.envs = {
				["SDK_TYPE"] = self["type"];
				["SDK_HOME"] = sdk.home;
				["SDK_INCLUDE"] = table.concat(self.include, ";");
				["SDK_LIB"] = table.concat(self.lib, ";");
				["SDK_DRIVER_SRC"] = sdk.exts["driver_src"];
				["SDK_CMSIS_DEVICES_SRC"] = sdk.exts["cmsis_src"];
				["SDK_MIDWARE"] = sdk.exts["midware"];
				["SDK_USB_LIB"] = sdk.exts["usb_lib"];
			};
			if JLINK_INFO and JLINK_INFO.bin then
				local segger_rtt_src_path = make_path(JLINK_INFO.bin, "../Samples/RTT");
				local file_name = utils:list_dir(segger_rtt_src_path.."/SEGGER_RTT_*.zip");
				if file_name then
					self.envs["SEGGER_RTT_SRC"] = make_path(segger_rtt_src_path, file_name);
				end;
			end;
			return self;
		end;
		["debug_adapter"] = debug_adapter;
	};
	table.sort(toolset.sdks);
	toolset:switch_version(default_ver, default_sdk);
	return {toolset};
end;

function xhsc_find_toolset_for_device(device_name)
	local extract_prefix;
	if device_name:sub(1, 6) == "hc32f" then
		extract_prefix = function(name) return name:sub(1,8); end;
	else
		extract_prefix = function(name) return name:sub(1,6); end;
	end;
	local proj_type_prefix = extract_prefix(device_name);
	local sdk_name = "XHSC";
	local sdk_version = nil;
	local toolset_version = nil;
	for idx, toolset in ipairs(TOOLSETS) do
		if toolset["type"] == "XHSC" then
			for ver, sdk in pairs(toolset["sdks_info"]) do
				local sdk_target_prefix = extract_prefix(sdk.target);
				if sdk_target_prefix == proj_type_prefix then
					-- found the sdk
					sdk_version = ver;
					break;
				end;
			end;
			if sdk_version then
				local toolset_names = table.keys(toolset["vers_info"]);
				table.sort(toolset_names);
				toolset_version = toolset_names[#toolset_names];
				sdk_name = toolset["name"];
				break;
			end;
		end;
	end;
	if sdk_version == nil or toolset_version == nil then
		return;
	end;
	return sdk_name.."|"..toolset_version.."|"..sdk_version;
end;

-- GD32 sdks
local function collect_gd32_sdks(root_path)
	local gd32_repo_root = make_path(root_path, "gd32");
	if not utils:file_time(gd32_repo_root) then
		return;
	end;
	local sdks = {};
	local default_sdk = nil;


	for idx, name in ipairs(utils:list_dir(make_path(gd32_repo_root, [[Firmware/*]]), true) or {}) do
		local target_type = name:lower():match("^(gd32[acefhl][x0-9]+)");
		if target_type then
			name = target_type;
			local sdk_target_name = target_type:lower();
			local sdk_root = make_path(gd32_repo_root, "Firmware", name);
			local sdk_std_driver_root = make_path(sdk_root, "firmware", sdk_target_name.."_standard_peripheral");
			local sdk_hal_driver_root = make_path(sdk_root, "firmware", sdk_target_name.."_hal_peripheral");
			if utils:is_dir(sdk_root) then
				local cmsis_inc_path = make_path(sdk_root, "firmware/cmsis/inc");
				local cmsis_src_path = make_path(sdk_root, "firmware/cmsis/src");
				local inc_file = make_path(cmsis_inc_path, name..".h");
				local inc_file_obj = io.u8_open(inc_file, "r");
				local sdk_version = "";
				if inc_file_obj == nil then
					inc_file = make_path(cmsis_inc_path, name:sub(1, #name-1).."x.h");
					inc_file_obj = io.u8_open(inc_file, "r");
				end;
				if inc_file_obj then
					for line in inc_file_obj:lines() do
						local ver = line:match([[^.*[%\@]version.*V([%d%.]+)]]);
						if ver then
							sdk_version = ver;
						end;
						if line == [[*/]] then
							break;
						end;
					end;
					inc_file_obj:close();
				end;
				local cpus = {};
				local sdk_version_info = sdk_target_name.."-"..sdk_version;
				local sdk = {
					home = sdk_root;
					version = sdk_version_info;
					target = sdk_target_name;
					include = {};
					lib = {};
					exts = {
						["driver_src"] = validate_path(make_path(sdk_std_driver_root, "src"), make_path(sdk_std_driver_root, "Source"));
						["hal_driver_src"] = validate_path(make_path(sdk_hal_driver_root, "src"), make_path(sdk_hal_driver_root, "Source"));
						["cmsis_src"] = validate_path(cmsis_src_path);
						["template_root"] = validate_path(make_path(sdk_root, "template"));
						["ldscript_root"] = validate_path(make_path(sdk_root, "ldscripts"));
					};
					["cpus"] = cpus;
				};

				append_valid_path(sdk.include, make_path(sdk_root, "inc"));
				append_valid_path(sdk.include, cmsis_inc_path);
				append_valid_path(sdk.include, make_path(sdk_std_driver_root, "inc"), make_path(sdk_std_driver_root, "Include"));
				append_valid_path(sdk.include, make_path(sdk_hal_driver_root, "inc"), make_path(sdk_hal_driver_root, "Include"));

				sdks[sdk.version] = sdk;
				if default_sdk == nil then
					default_sdk = sdk.version;
				end;
			end;
		end;
	end;
	return sdks, default_sdk, gd32_repo_root, "GD32";
end;

local function find_gd32_sdks(root_path, collect_func)
	if collect_func == nil then
		collect_func = collect_gd32_sdks;
	end;
	local sdks, default_sdk, gd32_repo_root, sdk_name = collect_func(root_path);
	if sdks == nil then
		return;
	end;
	local compilers = ARM_NONE_EABI_TOOLSETS;
	local compiler_names = table.keys(compilers);
	table.sort(compiler_names);
	local default_ver = compiler_names[1];
	if default_sdk == nil or default_ver == nil then
		return;
	end;
	local openocd_dir_name = utils:list_dir(make_path(root_path, "gd32/Tools/OpenOCD/xpack-openocd*"));
	local openocd_info = nil;
	if openocd_dir_name then
		local openocd_root = make_path(root_path, "gd32/Tools/OpenOCD", openocd_dir_name);
		openocd_info = {
			["root"] = openocd_root;
			["bin"] = make_path(openocd_root, "bin/openocd.exe");
			["scripts"] = make_path(openocd_root, "scripts");
		};
	end;
	
	local debug_adapter = toolset_make_debug_adapter({"JLink", "JLink(OpenOCD)", "CMSIS-DAP", "ST-Link", "ST-Link(OpenOCD)", "pyOCD"}, nil, openocd_info);
	local toolset = {
		["type"] = "GD32";
		["target"] = "arm-none-eabi";
		["name"] = sdk_name;
		["home"] = gd32_repo_root;
		encoding = "utf-8";
		suffixes = {
			exec = ".elf";
			shared = ".so";
			lib = ".a";
		},
		["sdks"] = table.keys(sdks);
		["sdks_info"] = sdks;
		["vers"] = compiler_names;
		["vers_info"] = compilers;
		["version"] = "";
		["sdk"] = "";
		["switch_version"] = function(self, compiler_ver, sdk_ver)
			local compiler = compilers[compiler_ver];
			local sdk = sdks[sdk_ver];
			self.bin = table.iclone({}, compiler.bin or {}, sdk.bin or {});
			self.include = table.iclone({}, (sdk.include or {}), (compiler.include or {}));
			self.lib = table.iclone({}, (sdk.lib or {}), (compiler.lib or {}));
			self.tools = table.clone(compiler.tools, (sdk.tools or {}));
			self.version = compiler_ver;
			self.sdk = sdk_ver;
			self.envs = {
				["SDK_TYPE"] = self["type"];
				["SDK_HOME"] = sdk.home;
				["SDK_INCLUDE"] = table.concat(self.include, ";");
				["SDK_LIB"] = table.concat(self.lib, ";");
				["SDK_HAL_DRIVER_SRC"] = sdk.exts["driver_src"];
				["SDK_DRIVER_SRC"] = sdk.exts["driver_src"];
				["SDK_CMSIS_DEVICES_SRC"] = sdk.exts["cmsis_src"];
				["SDK_MIDWARE"] = sdk.exts["midware"];
				["SDK_USB_LIB"] = sdk.exts["usb_lib"];
				["SDK_TEMPLATE_ROOT"] = sdk.exts["template_root"];
				["SDK_LDSCRIPT_ROOT"] = sdk.exts["ldscript_root"];
			};
			if JLINK_INFO and JLINK_INFO.bin then
				local segger_rtt_src_path = make_path(JLINK_INFO.bin, "../Samples/RTT");
				local file_name = utils:list_dir(segger_rtt_src_path.."/SEGGER_RTT_*.zip");
				if file_name then
					self.envs["SEGGER_RTT_SRC"] = make_path(segger_rtt_src_path, file_name);
				end;
			end;
			return self;
		end;
		["debug_adapter"] = debug_adapter;
		["find_svd"] = function(self, device)
			local svd_root = make_path(self.home, "svd");
			local main_name = device:sub(1, 8);
			local common_name = main_name:sub(1, 6).."x"..main_name:sub(8, 8);
			local svd_paths = {
				make_path(svd_root, gd32_get_sdk_type_for_device(device)..".svd"),
				make_path(svd_root, device..".svd"),
				make_path(svd_root, main_name..".svd"),
				make_path(svd_root, common_name..".svd"),
			};
			for idx, path in ipairs(svd_paths) do
				if utils:file_time(path) then
					return path;
				end;
			end;
			return nil;
		end;
	};
	table.sort(toolset.sdks);
	toolset:switch_version(default_ver, default_sdk);
	return {toolset};
end;

function gd32_get_sdk_type_for_device(device_name)
	local sdk_type = nil;
	local target_device_series = device_name:sub(1, 8);
	if target_device_series == "gd32a490" or
		target_device_series == "gd32a508" or
		target_device_series == "gd32e232" or
		target_device_series == "gd32e501" or
		target_device_series == "gd32e502" or
		target_device_series == "gd32f403" then
		sdk_type = target_device_series;
	elseif target_device_series:match("gd32[acelw].+") then
		sdk_type = target_device_series:sub(1,7).."x";
	elseif target_device_series:match("gd32f[13]%d0") then
		sdk_type = target_device_series:sub(1,6).."x0";
	elseif target_device_series:match("gd32f[123]0%d") then
		sdk_type = target_device_series:sub(1,6).."0x";
	else
		sdk_type = target_device_series:sub(1,6).."xx";
	end;
	return sdk_type;
end;

function gd32_find_toolset_for_device(device_name)
	local extract_prefix;
	local sdk_type = gd32_get_sdk_type_for_device(device_name);

	local sdk_name = "GD32";
	local sdk_version = nil;
	local toolset_version = nil;
	for idx, toolset in ipairs(TOOLSETS) do
		if toolset["type"] == "GD32" then
			for ver, sdk in pairs(toolset["sdks_info"]) do
				if sdk.target == sdk_type then
					-- found the sdk
					sdk_version = ver;
					break;
				end;
			end;
			if sdk_version then
				local toolset_names = table.keys(toolset["vers_info"]);
				table.sort(toolset_names);
				toolset_version = toolset_names[#toolset_names];
				sdk_name = toolset["name"];
				break;
			end;
		end;
	end;
	if sdk_version == nil or toolset_version == nil then
		return;
	end;
	return sdk_name.."|"..toolset_version.."|"..sdk_version;
end;

-- N32 sdks
local function collect_n32_sdks(root_path)
	local n32_repo_root = make_path(root_path, "nationstech");
	if not utils:file_time(n32_repo_root) then
		return;
	end;
	local sdks = {};
	local default_sdk = nil;

	for idx, name in ipairs(utils:list_dir(make_path(n32_repo_root, [[n32*_library.*]]), true) or {}) do
		local sdk_root = make_path(n32_repo_root, name);
		local target_type, sdk_version = name:lower():match("^(n32[gl][x0-9]+)_library%.(%d+%.%d+%.%d+)");
		if target_type then
			name = target_type;
			local sdk_target_name = target_type:lower();
			local cmsis_root = make_path(sdk_root, "firmware/cmsis");
			local sdk_driver_root = make_path(sdk_root, "firmware", sdk_target_name.."_std_periph_driver");
			local sdk_algo_lib_root = make_path(sdk_root, "firmware", sdk_target_name.."_algo_lib");
			local sdk_usbfs_root = make_path(sdk_root, "firmware", sdk_target_name.."_usbfs_driver");
			local sdk_periph_lib_root = make_path(sdk_root, "firmware", sdk_target_name.."_periph_lib");
			local cmsis_src_path = make_path(cmsis_root, "device");
			if utils:is_dir(sdk_driver_root) then
				local cpus = {};
				local sdk_version_info = sdk_target_name.."-"..sdk_version;
				local sdk = {
					home = sdk_root;
					version = sdk_version_info;
					target = sdk_target_name;
					include = {};
					lib = {};
					exts = {
						["driver_src"] = validate_path(make_path(sdk_driver_root, "src"));
						["cmsis_src"] = validate_path(cmsis_src_path);
						["usb_lib"] = validate_path(make_path(sdk_usbfs_root, "src"));
						["template_root"] = validate_path(cmsis_src_path);
					};
					["cpus"] = cpus;
				};

				append_valid_path(sdk.include, make_path(sdk_driver_root, "inc"));
				append_valid_path(sdk.include, make_path(sdk_usbfs_root, "inc"));
				append_valid_path(sdk.include, make_path(sdk_periph_lib_root, "inc"));
				append_valid_path(sdk.include, make_path(sdk_algo_lib_root, "inc"));
				append_valid_path(sdk.include, make_path(cmsis_root, "core"));
				append_valid_path(sdk.include, make_path(cmsis_root, "device"));
				append_valid_path(sdk.lib, make_path(sdk_periph_lib_root, "lib"));
				append_valid_path(sdk.lib, make_path(sdk_algo_lib_root, "lib"));
				
				sdks[sdk.version] = sdk;
				if default_sdk == nil then
					default_sdk = sdk.version;
				end;
			end;
		end;
	end;
	return sdks, default_sdk, n32_repo_root, "N32";
end;

local function find_n32_sdks(root_path, collect_func)
	if collect_func == nil then
		collect_func = collect_n32_sdks;
	end;
	local sdks, default_sdk, n32_repo_root, sdk_name = collect_func(root_path);
	if sdks == nil then
		return;
	end;
	local compilers = ARM_NONE_EABI_TOOLSETS;
	local compiler_names = table.keys(compilers);
	table.sort(compiler_names);
	local default_ver = compiler_names[1];
	if default_sdk == nil or default_ver == nil then
		return;
	end;
	
	local debug_adapter = toolset_make_debug_adapter({"JLink", "JLink(OpenOCD)", "CMSIS-DAP", "ST-Link", "ST-Link(OpenOCD)", "pyOCD"});
	local toolset = {
		["type"] = "N32";
		["target"] = "arm-none-eabi";
		["name"] = sdk_name;
		["home"] = n32_repo_root;
		encoding = "utf-8";
		suffixes = {
			exec = ".elf";
			shared = ".so";
			lib = ".a";
		},
		["sdks"] = table.keys(sdks);
		["sdks_info"] = sdks;
		["vers"] = compiler_names;
		["vers_info"] = compilers;
		["version"] = "";
		["sdk"] = "";
		["switch_version"] = function(self, compiler_ver, sdk_ver)
			local compiler = compilers[compiler_ver];
			local sdk = sdks[sdk_ver];
			self.bin = table.iclone({}, compiler.bin or {}, sdk.bin or {});
			self.include = table.iclone({}, (sdk.include or {}), (compiler.include or {}));
			self.lib = table.iclone({}, (sdk.lib or {}), (compiler.lib or {}));
			self.tools = table.clone(compiler.tools, (sdk.tools or {}));
			self.version = compiler_ver;
			self.sdk = sdk_ver;
			self.envs = {
				["SDK_TYPE"] = self["type"];
				["SDK_HOME"] = sdk.home;
				["SDK_INCLUDE"] = table.concat(self.include, ";");
				["SDK_LIB"] = table.concat(self.lib, ";");
				["SDK_HAL_DRIVER_SRC"] = sdk.exts["driver_src"];
				["SDK_DRIVER_SRC"] = sdk.exts["driver_src"];
				["SDK_CMSIS_DEVICES_SRC"] = sdk.exts["cmsis_src"];
				["SDK_MIDWARE"] = sdk.exts["midware"];
				["SDK_USB_LIB"] = sdk.exts["usb_lib"];
				["SDK_TEMPLATE_ROOT"] = sdk.exts["template_root"];
				["SDK_LDSCRIPT_ROOT"] = sdk.exts["template_root"];
			};
			if JLINK_INFO and JLINK_INFO.bin then
				local segger_rtt_src_path = make_path(JLINK_INFO.bin, "../Samples/RTT");
				local file_name = utils:list_dir(segger_rtt_src_path.."/SEGGER_RTT_*.zip");
				if file_name then
					self.envs["SEGGER_RTT_SRC"] = make_path(segger_rtt_src_path, file_name);
				end;
			end;
			return self;
		end;
		["debug_adapter"] = debug_adapter;
		["find_svd"] = function(self, device)
			local svd_root = make_path(self.home, "svd");
			local main_name = device:sub(1, 8);
			local common_name = main_name:sub(1, 6).."x"..main_name:sub(8, 8);
			local svd_paths = {
				make_path(svd_root, n32_get_sdk_type_for_device(device)..".svd"),
				make_path(svd_root, device..".svd"),
				make_path(svd_root, main_name..".svd"),
				make_path(svd_root, common_name..".svd"),
			};
			for idx, path in ipairs(svd_paths) do
				if utils:file_time(path) then
					return path;
				end;
			end;
			return nil;
		end;
	};
	table.sort(toolset.sdks);
	toolset:switch_version(default_ver, default_sdk);
	return {toolset};
end;

function n32_get_sdk_type_for_device(device_name)
	local sdk_type = nil;
	local target_device_series = device_name:sub(1, 7);
	if target_device_series == "n32g030" or
		target_device_series == "n32g031" or
		target_device_series == "n32g032" or
		target_device_series == "n32g401" or
		target_device_series == "n32g430" then
		sdk_type = target_device_series;
	elseif target_device_series:match("n32[gl].+") then
		sdk_type = target_device_series:sub(1,6).."x";
	else
		sdk_type = target_device_series:sub(1,6).."x";
	end;
	return sdk_type;
end;

function n32_find_toolset_for_device(device_name)
	local extract_prefix;
	local sdk_type = n32_get_sdk_type_for_device(device_name);

	local sdk_name = "N32";
	local sdk_version = nil;
	local toolset_version = nil;
	for idx, toolset in ipairs(TOOLSETS) do
		if toolset["type"] == "N32" then
			for ver, sdk in pairs(toolset["sdks_info"]) do
				if sdk.target == sdk_type then
					-- found the sdk
					sdk_version = ver;
					break;
				end;
			end;
			if sdk_version then
				local toolset_names = table.keys(toolset["vers_info"]);
				table.sort(toolset_names);
				toolset_version = toolset_names[#toolset_names];
				sdk_name = toolset["name"];
				break;
			end;
		end;
	end;
	if sdk_version == nil or toolset_version == nil then
		return;
	end;
	return sdk_name.."|"..toolset_version.."|"..sdk_version;
end;

-- TI simplelink sdk
local function find_ti_toolsets(root_path)
	if root_path == nil then
		root_path = find_reg_val([[HKLM\SOFTWARE\\Texas Instruments]], "CCS Theia", "Location");
	end;
	if root_path == nil then
		return;
	end;
	local tools = utils:list_dir(make_path(root_path, "*"), true);
	local compilers = {};
	local default_ver = nil;
	local sdks = {};
	local default_sdk = nil;
	if tools == nil then
		return;
	end;
	for i, name in ipairs(tools) do
		if name:match([[^gcc%-arm%-none%-eabi_[0-9_]+]]) then
			-- arm-none-eabi gcc
			local gcc_root = make_path(root_path, name);
			local gcc_bin_root = make_path(gcc_root, "bin");
			local gcc_version = utils:list_dir(make_path(gcc_root, [[lib/gcc/arm-none-eabi/*]]));
			local compiler = {
				version = "gcc-"..gcc_version;
				root = gcc_root;
				bin = {
					gcc_bin_root
				};
				include = {
					make_path(gcc_root, [[lib/gcc/arm-none-eabi]], gcc_version, [[include]])
				};
				tools = {
					["gcc"] = true;
					["g++"] = true;
					["cc"] = make_path(gcc_bin_root, [[arm-none-eabi-gcc.exe]])..";"..gcc_extra_flags.." -nostartfiles -static";
					["cxx"] = make_path(gcc_bin_root, [[arm-none-eabi-g++.exe]])..";"..gcc_extra_flags.." -nostartfiles -static";
					["gdb"] = make_path(gcc_bin_root, [[arm-none-eabi-gdb.exe]]);
					["debugger"] = "jlink";
					["vcpkg"] = "DISABLED";
				};
			};
			compilers[compiler.version] = compiler;
			if default_ver == nil then
				default_ver = compiler.version;
			end;
		elseif name:match([[^ti_cgt_tiarmclang%_[0-9_]+]]) then
			-- TiARMCC/arm-none-eabi clang
			local clang_root = make_path(root_path, name);
			local clang_bin_root = make_path(clang_root, "bin");
			local clang_version = utils:list_dir(make_path(clang_root, [[lib/clang/*]]));
			local compiler = {
				version = "tiarmclang-"..clang_version;
				root = clang_bin_root;
				bin = {
					clang_bin_root
				};
				include = {
					make_path(clang_root, [[lib/clang]], clang_version, [[include]]);
					make_path(clang_root, [[include/c]]);
					make_path(clang_root, [[include/c++/v1]]);
				};
				tools = {
					["clang"] = true;
					["clang++"] = true;
					["cc"] = make_path(clang_bin_root, [[tiarmclang.exe]])..";"..clang_extra_flags;
					["cxx"] = make_path(clang_bin_root, [[tiarmclang.exe]])..";"..clang_extra_flags;
					["debugger"] = "openocd";
					["vcpkg"] = "DISABLED";
				};
			};
			compilers[compiler.version] = compiler;
			if default_ver == nil then
				default_ver = compiler.version;
			end;
		elseif name:match([[^simplelink_cc.+_sdk_[0-9_]+]]) then
			-- Simplelink SDK
			local sdk_root = make_path(root_path, name);
			local sdk_type, sdk_major_ver, sdk_minor_ver, sdk_patch_ver1, sdk_patch_ver2 = name:match([[^simplelink_(cc.+)_sdk_([0-9]+)_([0-9]+)_([0-9]+)_([0-9]+)]]);
			local sdk_version = sdk_type:upper().."-"..table.concat({sdk_major_ver, sdk_minor_ver, sdk_patch_ver1, sdk_patch_ver2}, ".");
			local sdk = {
				version = sdk_version;
				target = sdk_type;
				include = {
					make_path(sdk_root, [[source]]);
				};
				lib = {
					make_path(sdk_root, [[source]]);
				};
			};
			sdks[sdk.version] = sdk;
			if default_sdk == nil then
				default_sdk = sdk.version;
			end;
		end;
	end;
	local debug_adapter = toolset_make_debug_adapter({"JLink", "JLink(OpenOCD)"});
	local toolset = {
		["type"] = "TI-SimpleLink";
		["target"] = "arm-none-eabi";
		["name"] = "TI-SimpleLink";
		["home"] = root_path;
		encoding = "utf-8";
		suffixes = {
			exec = ".elf";
			shared = ".so";
			lib = ".a";
		},
		["sdks"] = table.keys(sdks);
		["sdks_info"] = sdks;
		["vers"] = table.keys(compilers);
		["vers_info"] = compilers;
		["version"] = "";
		["sdk"] = "";
		["switch_version"] = function(self, compiler_ver, sdk_ver)
			local compiler = compilers[compiler_ver];
			local sdk = sdks[sdk_ver];
			self.bin = table.clone({}, compiler.bin or {}, sdk.bin or {});
			self.include = table.clone({}, sdk.include or {}, compiler.include or {});
			self.lib = table.clone({}, sdk.lib or {}, compiler.lib or {});
			self.tools = table.clone(compiler.tools);
			self.version = compiler_ver;
			self.sdk = sdk_ver;
			self.envs = {
				["SDK_TYPE"] = self["type"];
				["SDK_HOME"] = sdk.home;
				["SDK_INCLUDE"] = table.concat(self.include, ";");
				["SDK_LIB"] = table.concat(self.lib, ";");
				-- ["SDK_HAL_DRIVER_SRC"] = sdk.exts["hal_driver_src"];
				-- ["SDK_CMSIS_DEVICES_ST_SRC"] = sdk.exts["cmsis_devices_st_src"];
				-- ["SDK_RTOS_SRC"] = sdk.exts["rtos_src"];
				-- ["SDK_RTOS2_SRC"] = sdk.exts["rtos2_src"];
			};
			return self;
		end;
		["debug_adapter"] = debug_adapter;
	};
	toolset:switch_version(default_ver, default_sdk);
	return {toolset};
end;

-- RP2040 c-sdk
local function collect_pico_sdks(root_path)
	local pico_sdk_root = root_path and make_path(root_path, "pico/sdk");
	if not pico_sdk_root or not utils:file_time(pico_sdk_root) then
		pico_sdk_root = os.getenv("PICO_SDK_PATH");
	end;
	if not pico_sdk_root or not utils:file_time(pico_sdk_root) then
		return;
	end;
	local pico_sdk_version_cmake_path = make_path(pico_sdk_root, "pico_sdk_version.cmake");
	local pico_sdk_version_cmake = io.u8_open(pico_sdk_version_cmake_path, "r");
	if pico_sdk_version_cmake == nil then
		return;
	end;
	local pico_ver = {
		major = 0;
		minor = 0;
		revision = 0;
	};
	for line in pico_sdk_version_cmake:lines() do
		local ver_type, ver_val = line:match("^%s*set%(%s*PICO_SDK_VERSION_(%w+)%s+(%d+)%s*%)");
		if ver_type and ver_val then
			pico_ver[ver_type:lower()] = tonumber(ver_val);
		end;
	end;
	pico_sdk_version_cmake:close();
	local pico_sdk_version = string.format("%d.%d.%d", pico_ver.major, pico_ver.minor, pico_ver.revision);
	local sdks = {};
	sdks[pico_sdk_version] = {
		home = pico_sdk_root;
		version = pico_sdk_version;
		target = "PICO";
		include = {};
		lib = {};
		exts = {
		};
	};
	local default_sdk = pico_sdk_version;
	return sdks, default_sdk, pico_sdk_root, "PicoSDK";
end;

local function find_pico_sdks(root_path, collect_func)
	if collect_func == nil then
		collect_func = collect_pico_sdks;
	end;
	local sdks, default_sdk, pico_sdk_root, sdk_type = collect_func(root_path);
	if not sdks then
		return nil;
	end;

	local compilers = ARM_NONE_EABI_TOOLSETS;
	local compiler_names = table.keys(compilers);
	table.sort(compiler_names);
	local default_ver = compiler_names[1];
	if default_sdk == nil or default_ver == nil then
		return;
	end;

	local debug_adapter = toolset_make_debug_adapter({"JLink", "JLink(OpenOCD)", "CMSIS-DAP", "ST-Link", "ST-Link(OpenOCD)", "pyOCD"});
	local toolset = {
		["type"] = "PicoSDK";
		["target"] = "PICO";
		["name"] = "PicoSDK";
		["home"] = pico_sdk_root;
		encoding = "utf-8";
		suffixes = {
			exec = ".elf";
			shared = ".so";
			lib = ".a";
		},
		["sdks"] = table.keys(sdks);
		["sdks_info"] = sdks;
		["vers"] = compiler_names;
		["vers_info"] = compilers;
		["version"] = "";
		["sdk"] = "";
		["switch_version"] = function(self, compiler_ver, sdk_ver)
			local compiler = compilers[compiler_ver];
			local sdk = sdks[sdk_ver];
			self.bin = table.iclone({}, compiler.bin or {}, sdk.bin or {});
			self.include = table.iclone({}, (sdk.include or {}), (compiler.include or {}));
			self.lib = table.iclone({}, (sdk.lib or {}), (compiler.lib or {}));
			self.tools = table.clone(compiler.tools);
			self.version = compiler_ver;
			self.sdk = sdk_ver;
			self.envs = {
				["SDK_TYPE"] = self["type"];
				["SDK_HOME"] = sdk.home;
				["SDK_INCLUDE"] = table.concat(self.include, ";");
				["SDK_LIB"] = table.concat(self.lib, ";");
				["PICO_SDK_PATH"] = sdk.home;
				["PICO_TOOLCHAIN_PATH"] = compiler.root;
			};
			if JLINK_INFO and JLINK_INFO.bin then
				local segger_rtt_src_path = make_path(JLINK_INFO.bin, "../Samples/RTT");
				local file_name = utils:list_dir(segger_rtt_src_path.."/SEGGER_RTT_*.zip");
				if file_name then
					self.envs["SEGGER_RTT_SRC"] = make_path(segger_rtt_src_path, file_name);
				end;
			end;
			return self;
		end;
		["debug_adapter"] = debug_adapter;
	};
	table.sort(toolset.sdks);
	toolset:switch_version(default_ver, default_sdk);
	return {toolset};
end;

function pico_find_toolset_for_device(device_name)
	local sdk_name = "PicoSDK";
	local sdk_version = nil;
	local toolset_version = nil;
	for idx, toolset in ipairs(TOOLSETS) do
		if toolset["type"] == "PicoSDK" then
			sdk_version = toolset["sdk"];
			toolset_version = toolset["version"];
			break;
		end;
	end;
	if sdk_version == nil or toolset_version == nil then
		return;
	end;
	return sdk_name.."|"..toolset_version.."|"..sdk_version;
end;

-------------------------------------------------------------------------------------

local function __ssh_make_toolset(toolinfo)
	local home = toolinfo.home;
	local project_home = make_path(home, ".edx"):gsub("\\", "/");

	local ssh_cache_home = edx.make_config_path("ssh-cache", CFG_PATH_LOCAL_APPDATA);
	local remote_name = string.gsub(toolinfo.remote, "ssh://", "");
	remote_name = remote_name:gsub(":", "_");
	local ssh_local_root_path = make_path(ssh_cache_home, remote_name);

	toolinfo["map_path"] = function(path)
		path = path:gsub("\\", "/");
		return path:gsub("^\"?(%w)(:)", function(a)
				return home.."/.edx/"..(a):lower();
		end);
	end;

	toolinfo["unmap_path"] = function(path, alter_local_root_path)
		if path:sub(1, #project_home) == project_home then
			path = path:gsub(".-/.edx/(%w)/(.+)", function(a, b)
					return (a:upper()..":\\"..b):gsub("/", "\\");
			end);
		elseif path:sub(1,1) == "/" then
			if alter_local_root_path then
				path = make_path(alter_local_root_path, path);
			else
				path = make_path(ssh_local_root_path, path);
			end;
		end;
		return path;
	end;

	toolinfo["get_pretty_print_path"] = function(self)
		return self.tools["gcc-pretty-print"];
	end;
	return toolinfo;
end;

function search_compilers(callback)
	local user_profile_root = os.getenv("USERPROFILE");
	CLANGD_PATHS = {};
	NINJA_PATHS = {};
	GDB_PATHS = {};
	CMAKE_PATHS = {};
	CCACHE_PATHS = {};
	VCPKG_PATHS = {};
	XMAKE_PATHS = {};

	local toolsets = {};
	local winsdk = prepare_winsdk_info();
	local vs_info = pinvoke(get_vs_info, 17, winsdk);
	local vs_info_2019 = pinvoke(get_vs_info, 16, winsdk);
	if vs_info == nil then
		vs_info = vs_info_2019;
		vs_info_2019 = nil;
	end;

	if vs_info == nil then
		-- vs2022/vs2019 not found, fallback to vs buildtools
		local __vs_paths = find_vs_paths();
		for idx, path in ipairs(__vs_paths) do
			local info = {
				root = path;
			};
			if path:match([[.+\2022\.+]]) then
				info.version = 17;
				vs_info = pinvoke(get_vs_info, info, winsdk);
			elseif path:match([[.+\2019\.+]]) then
				info.version = 16;
				vs_info = pinvoke(get_vs_info, info, winsdk);
			end;
			if vs_info then
				break;
			end;
		end;
	end;

	if vs_info then
		if vs_info.x64tools ~= nil then
			table.insert(toolsets, vs_info.x64tools);
		end ;

		if vs_info.x86tools ~= nil then
			table.insert(toolsets, vs_info.x86tools);
		end;

		if vs_info_2019 then
			if vs_info_2019.x64tools ~= nil then
				table.insert(toolsets, vs_info_2019.x64tools);
			end ;

			if vs_info.x86tools ~= nil then
				table.insert(toolsets, vs_info_2019.x86tools);
			end;
		end;

		local clang_tools = pinvoke(find_clang_tools, vs_info);
		if clang_tools ~= nil then
			table.append(toolsets, table.unpack(clang_tools));
		end;

		local intel_tools = pinvoke(find_intel_tools, vs_info);
		if intel_tools then
			table.append(toolsets, table.unpack(intel_tools));
		end;
	else
		-- vs not found, find clangd only
		pinvoke(find_clang_tools, {root=""});
	end;

	local mingw_tools = pinvoke(find_mingw_tools);
	if mingw_tools then
		table.append(toolsets, table.unpack(mingw_tools));
	end;

	local llvm_msys2_tools = pinvoke(find_llvm_msys2_tools);
	if llvm_msys2_tools then
		table.append(toolsets, table.unpack(llvm_msys2_tools));
	end;

	local llvm_mingw_tools = pinvoke(find_llvm_mingw_tools);
	if llvm_mingw_tools then
		table.append(toolsets, table.unpack(llvm_mingw_tools));
	end;

	-- search gcc by PATH environment
	for idx, gcc_path in ipairs({utils:search_env("gcc.exe", "PATH", "*")}) do
		local gcc_home = make_path(gcc_path, "../..");
		local exists_tool = find_first(toolsets, function(tool) return string.lower(tool.home) == string.lower(gcc_home); end);
		if not exists_tool then
			local new_tool = pinvoke(search_local_toolset, make_path(gcc_path, ".."));
			if new_tool then
				toolsets = table.iclone(toolsets, new_tool);
			end;
		end;
	end;

	local wsl_tools = pinvoke(find_wsl_tools);
	if wsl_tools then
		table.append(toolsets, table.unpack(wsl_tools));
	end;

	pinvoke(find_jlink_tools);
	pinvoke(find_t_head_debugserver);

	local edx_stm32cube_path;
	local edx_airmcu_path;
	local edx_picosdk_path;
	local edx_xhsc_path;
	local edx_gd32_path;
	local edx_n32_path;
	local edx_fm33_path;
	for i, toolset_root in ipairs(EDX_TOOLSETS_ROOT) do
		for i, folder_name in ipairs(utils:list_dir(make_path(toolset_root, "*"), true) or {}) do
			local lower_folder_name = folder_name:lower();
			if lower_folder_name:match("^xmake.*") then
				local xmake_bin = make_path(toolset_root, folder_name, "xmake.exe");
				if utils:file_time(xmake_bin) then
					table.insert(XMAKE_PATHS, xmake_bin);
				end;
			elseif lower_folder_name:match("^openocd.*") then
				pinvoke(find_openocd, toolset_root, folder_name);
			elseif lower_folder_name:match("^pyocd.*") then
				pinvoke(find_pyocd, toolset_root, folder_name);
			elseif lower_folder_name:match("^stlink%-gdbserver.*") then
				STLINK_INFO.bin = make_path(toolset_root, folder_name, "bin/ST-LINK_gdbserver.exe");
			elseif lower_folder_name:match("^stm32%-programmer.*") then
				STLINK_INFO.prog_bin = make_path(toolset_root, folder_name, "bin/STM32_Programmer_CLI.exe");
			elseif lower_folder_name:match("^stm32cube$") then
				edx_stm32cube_path = toolset_root;
			elseif lower_folder_name:match("^airmcu$") then
				edx_airmcu_path = toolset_root;
			elseif lower_folder_name:match("^pico$") then
				edx_picosdk_path = toolset_root;
			elseif lower_folder_name:match("^xhsc$") then
				edx_xhsc_path = toolset_root;
			elseif lower_folder_name:match("^gd32$") then
				edx_gd32_path = toolset_root;
			elseif lower_folder_name:match("^fm33$") then
				edx_fm33_path = toolset_root;
			elseif lower_folder_name:match("^nationstech$") then
				edx_n32_path = toolset_root;
			elseif lower_folder_name:match("^lua%-language%-server.*") then
				local lua_language_server = make_path(toolset_root, folder_name, "bin", "lua-language-server.exe");
				local lua_language_server_script = make_path(toolset_root, folder_name, "main.lua");
				if utils:file_time(lua_language_server_script) and utils:file_time(lua_language_server) then
					LUA_LANGUAGE_SERVER = lua_language_server;
				end;
			else
				pinvoke(find_arm_eabi_toolsets, toolset_root, folder_name);
			end;
		end;
	end;

	pinvoke(find_armclang_toolsets);

	if STLINK_INFO == nil or STLINK_INFO.bin == nil or STLINK_INFO.prog_bin == nil then
		STLINK_INFO = nil;
	else
		print("Found ST-Link Tools:\n\tGDBServer => ", STLINK_INFO.bin, "\n\tProgrammer => ", STLINK_INFO.prog_bin);
	end;
	if OPENOCD_INFO and OPENOCD_INFO.bin then
		print("Found OpenOCD:\n\t=> ", OPENOCD_INFO.bin);
	end;
	if PYOCD_INFO == nil then
		local pyocd_bin = utils:search_env("pyocd.exe", "PATH");
		if pyocd_bin then
			PYOCD_INFO = {
				root = make_path(pyocd_bin, "..");
				bin = pyocd_bin;
			};
		end;
	end;
	if PYOCD_INFO and PYOCD_INFO.bin then
		print("Found pyOCD:\n\t=> ", PYOCD_INFO.bin);
	end;
	if JLINK_INFO and JLINK_INFO.bin then
		print("Found J-Link:\n\t=> ", JLINK_INFO.bin);
	end;
	if THEAD_DEBUGSERVER_INFO and THEAD_DEBUGSERVER_INFO.bin then
		print("Found T-Head DebugServer:\n\t=> ", THEAD_DEBUGSERVER_INFO.bin);
	end;
	if ARM_NONE_EABI_TOOLSETS ~= nil then
		local arm_toolsets = table.values(ARM_NONE_EABI_TOOLSETS);
		if #arm_toolsets > 0 then
			table.sort(arm_toolsets, function(a,b) return a.version < b.version; end);
			print("Found ARM C/C++ Compiler(s):");
			for idx, info in ipairs(arm_toolsets) do
				print("\t", idx, ":", info.version, " => ", info.root);
			end;
		end;
	end;

	dbg_call(function() require "esp32_idf_support"; end, LANG("notify/error/init_esp32_idf_failed"));
	dbg_call(function() require "bouffalo_support"; end, LANG("notify/error/init_bouffalo_sdk_failed"));
	dbg_call(function() require "hpm_support"; end, LANG("notify/error/init_hpm_sdk_failed"));
	dbg_call(function() require "zephyr_support"; end, LANG("notify/error/init_zephyr_sdk_failed"));

	local ti_toolsets = dbg_call(find_ti_toolsets, LANG("notify/error/init_ti_sdk_failed"));
	if ti_toolsets then
		table.append(toolsets, table.unpack(ti_toolsets));
	end;

	if user_profile_root then
		local stm32cube_sdks = pinvoke(find_stm32cube_sdks, user_profile_root);
		if stm32cube_sdks then
			table.append(toolsets, table.unpack(stm32cube_sdks));
		end;
	end;
	
	if edx_stm32cube_path then
		local stm32cube_sdks = pinvoke(find_stm32cube_sdks, edx_stm32cube_path, collect_stm32cube_sdks_edx);
		if stm32cube_sdks then
			table.append(toolsets, table.unpack(stm32cube_sdks));
		end;
	end;
	
	if edx_airmcu_path then
		local airmcu_sdks = pinvoke(find_airmcu_sdks, edx_airmcu_path, collect_airmcu_sdks);
		if airmcu_sdks then
			table.append(toolsets, table.unpack(airmcu_sdks));
		end;
	end;

	if edx_xhsc_path then
		local xhsc_sdks = pinvoke(find_xhsc_sdks, edx_xhsc_path, collect_xhsc_sdks);
		if xhsc_sdks then
			table.append(toolsets, table.unpack(xhsc_sdks));
		end;
	end;

	if edx_gd32_path then
		local gd32_sdks = pinvoke(find_gd32_sdks, edx_gd32_path, collect_gd32_sdks);
		if gd32_sdks then
			table.append(toolsets, table.unpack(gd32_sdks));
		end;
	end;

	if edx_n32_path then
		local n32_sdks = pinvoke(find_n32_sdks, edx_n32_path, collect_n32_sdks);
		if n32_sdks then
			table.append(toolsets, table.unpack(n32_sdks));
		end;
	end;

	if edx_fm33_path then
		local fm33_sdks = pinvoke(find_fm33_sdks, edx_fm33_path, collect_fm33_sdks);
		if fm33_sdks then
			table.append(toolsets, table.unpack(fm33_sdks));
		end;
	end;

	do
		local pico_sdks = pinvoke(find_pico_sdks, edx_picosdk_path, collect_pico_sdks);
		if pico_sdks then
			table.append(toolsets, table.unpack(pico_sdks));
		end;
	end;

	if not NINJA_PATHS[1] then
		table.append(NINJA_PATHS, utils:search_env("ninja.exe", "PATH"));
	end;
	if not GDB_PATHS[1] then
		table.append(GDB_PATHS, utils:search_env("gdb.exe", "PATH"));
	end;
	if not CMAKE_PATHS[1] then
		table.append(CMAKE_PATHS, utils:search_env("cmake.exe", "PATH"));
	end;
	if not CCACHE_PATHS[1] then
		table.append(CCACHE_PATHS, utils:search_env("ccache.exe", "PATH"));
	end;
	if not VCPKG_PATHS[1] then
		table.append(VCPKG_PATHS, utils:search_env("vcpkg.exe", "PATH"));
	end;
	if not XMAKE_PATHS[1] then
		table.append(XMAKE_PATHS, utils:search_env("xmake.exe", "PATH"));
	end;
	
	if NINJA_PATHS[1] then
		NINJA_PATH = NINJA_PATHS[1];
	else
		NINJA_PATH = [[ninja]];
	end;

	if CLANGD_PATHS[1] then
		CLANGD_PATH = CLANGD_PATHS[1];
	else
		CLANGD_PATH = [[clangd]];
		print("\x1b[33;41m", LANG("Clangd not found! LSP may not work!"), "\x1b[0m");
		print("\x1b[33;41m", LANG("Download from https://llvm.org"), "\x1b[0m");
	end;

	if CCACHE_PATHS[1] then
		CCACHE_PATH = CCACHE_PATHS[1];
	end;

	XMAKE_PATHS = table.unique(XMAKE_PATHS);

	if not VCPKG_PATHS[1] then
		local vcpkg_root = os.getenv("VCPKG_ROOT");
		local vcpkg_path = vcpkg_root and make_path(vcpkg_root, "vcpkg.exe");
		if vcpkg_path and utils:file_time(vcpkg_path) then
			VCPKG_PATHS[1] = vcpkg_path;
		end;
	end;

	if VCPKG_PATHS[1] then
		VCPKG_PATH = VCPKG_PATHS[1];
		VCPKG_TOOLCHAIN_FILE = make_path(VCPKG_PATH, "../scripts/buildsystems/vcpkg.cmake");
		if not utils:file_time(VCPKG_TOOLCHAIN_FILE) then
			VCPKG_TOOLCHAIN_FILE = nil;
		end;
		local VCPKG_PORTS = nil;
		VCPKG_GET_PORTS = function()
			if VCPKG_PORTS ~= nil then
				return VCPKG_PORTS;
			end;
			VCPKG_PORTS = utils:list_dir(make_path(VCPKG_PATH, "../ports"), true) or {};
		end;
	end;

	if CMAKE_LANGUAGE_SERVER == nil then
		CMAKE_LANGUAGE_SERVER = utils:search_env("neocmakelsp-x86_64-pc-windows-msvc.exe", "PATH");
	end;

	if callback then
		callback(toolsets);
	end;
	return toolsets;
end;

function collect_custom_toolsets()
	local custom_toolsets = {};
	for idx, toolset in ipairs(TOOLSETS) do
		if toolset.custom then
			table.insert(custom_toolsets, {
					["name"] = toolset["name"];
					["home"] = toolset["home"];
					["wsl"] = toolset["wsl"];
					["remote"] = toolset["remote"];
					["version"] = toolset["version"];
					["system"] = toolset["system"];
					["type"] = toolset["type"];
					["target"] = toolset["target"];
					["custom"] = toolset["custom"];
					["encoding"] = toolset["encoding"];
					["bin"] = table.clone(toolset["bin"]);
					["include"] = table.clone(toolset["include"]);
					["lib"] = table.clone(toolset["lib"]);
					["tools"] = table.clone(toolset["tools"]);
			});
		end;
	end;
	return custom_toolsets;
end;

function update_custom_toolsets(custom_toolsets)
	if #TOOLSETS > 0 then
		for idx = #TOOLSETS, 1, -1 do
			local toolset = TOOLSETS[idx];
			if toolset.custom then
				table.remove(TOOLSETS, idx);
			end;
		end;
	end;

	if custom_toolsets == nil then
		return;
	end;

	for idx, toolset in ipairs(custom_toolsets) do
		local new_toolset = table.clone({custom = true}, toolset);
		if new_toolset.remote then
			new_toolset = __ssh_make_toolset(new_toolset);
		end;
		table.insert(TOOLSETS, new_toolset);
	end;

	local toolset_names = {};
	for idx, toolset in ipairs(TOOLSETS) do
		local name = toolset.name;
		if toolset_names[name] then
			toolset_names[name] = toolset_names[name] + 1;
			toolset.name = name..string.format("[%u]", toolset_names[name] - 1);
		else
			toolset_names[name] = 1;
		end;
	end;
end;

function search_local_toolset(bin_path)
	local toolsets = {};
	find_mingw_tools_in_path(bin_path, toolsets);
	if #toolsets > 0 then
		return toolsets;
	end;
	find_llvm_mingw_tools_in_path(bin_path, toolsets);
	if #toolsets > 0 then
		return toolsets;
	end;
	find_llvm_msys2_tools_in_path(bin_path, toolsets);
	if #toolsets > 0 then
		return toolsets;
	end;
	return nil;
end;
