--!A cross-platform build utility based on Lua -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. -- You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. -- -- Copyright (C) 2015-present, Xmake Open Source Community. -- -- @author OpportunityLiu -- @file vsxmake.lua -- -- imports import("core.base.option") import("core.base.hashset") import("vstudio.impl.vsinfo", { rootdir = path.directory(os.scriptdir()) }) import("render") import("getinfo") import("core.project.config") import("core.cache.localcache") import("vstudio.impl.vsutils", {rootdir = path.join(os.programdir(), "plugins", "project")}) local template_root = path.join(os.programdir(), "scripts", "vsxmake", "vsproj", "templates") local template_sln = path.join(template_root, "sln", "vsxmake.sln") local template_vcx = path.join(template_root, "vcxproj", "#target#.vcxproj") local template_csproj = path.join(template_root, "csproj", "#target#.csproj") local template_fil = path.join(template_root, "vcxproj.filters", "#target#.vcxproj.filters") local template_props = path.join(template_root, "Xmake.Custom.props") local template_targets = path.join(template_root, "Xmake.Custom.targets") local template_items = path.join(template_root, "Xmake.Custom.items") local template_itemfil = path.join(template_root, "Xmake.Custom.items.filters") -- the source file extensions natively supported by the vsxmake project, keyed by the template import name -- the `filenone` group collects everything else (e.g. files added by add_files but handled by a custom rule) local _source_exts = { filec = {".c"}, filecxx = {".cpp", ".cc", ".cxx"}, filempp = {".mpp", ".mxx", ".cppm", ".ixx"}, filecu = {".cu"}, fileobj = {".obj", ".o"}, filerc = {".rc"}, fileui = {".ui"}, fileqrc = {".qrc"}, filets = {".ts"}, filecs = {".cs"} } function _filter_files(files, includeexts, excludeexts) local positive = not excludeexts local extset = hashset.from(positive and includeexts or excludeexts) local f = {} for _, file in ipairs(files) do local ext = path.extension(file) if (positive and extset:has(ext)) or not (positive or extset:has(ext)) then table.insert(f, file) end end table.sort(f) return f end function _buildparams(info, target, default) local function getprop(match, opt) local i = info local r = info[match] if target then opt = table.join(target, opt) end for _, k in ipairs(opt) do local v = (i._targets or {})[k] if v == nil and i._arch_modes then v = i._arch_modes[k] end if v == nil and i._paths then v = i._paths[k] end if v == nil and i._dirs then v = i._dirs[k] end if v == nil and i._deps then v = i._deps[k] end if v == nil and i._groups then v = i._groups[k] end if v == nil and i._group_deps then v = i._group_deps[k] end if v == nil then v = i[k] end if v == nil then raise("key '" .. k .. "' not found") end i = v r = i[match] or r end return r or default end local function listconfig(args) for _, k in ipairs(args) do args[k] = true end local r = {} if args.target then table.insert(r, info.targets) end if args.mode then table.insert(r, info.modes) end if args.arch then table.insert(r, info.archs) end if args.group then table.insert(r, info.groups) end if args.group_dep then table.insert(r, info.group_deps) end if args.dir then table.insert(r, info._targets[target].dirs) end if args.dep then table.insert(r, info._targets[target].deps) end if args.filec then local files = info._targets[target].sourcefiles table.insert(r, _filter_files(files, _source_exts.filec)) elseif args.filecxx then local files = info._targets[target].sourcefiles table.insert(r, _filter_files(files, _source_exts.filecxx)) elseif args.filempp then local files = info._targets[target].sourcefiles table.insert(r, _filter_files(files, _source_exts.filempp)) elseif args.filecu then local files = info._targets[target].sourcefiles table.insert(r, _filter_files(files, _source_exts.filecu)) elseif args.fileobj then local files = info._targets[target].sourcefiles table.insert(r, _filter_files(files, _source_exts.fileobj)) elseif args.filerc then local files = info._targets[target].sourcefiles table.insert(r, _filter_files(files, _source_exts.filerc)) elseif args.fileui then -- for qt/.ui local files = info._targets[target].sourcefiles table.insert(r, _filter_files(files, _source_exts.fileui)) elseif args.fileqrc then -- for qt/.qrc local files = info._targets[target].sourcefiles table.insert(r, _filter_files(files, _source_exts.fileqrc)) elseif args.filets then -- for qt/.ts local files = info._targets[target].sourcefiles table.insert(r, _filter_files(files, _source_exts.filets)) elseif args.filecs then -- for c# local files = info._targets[target].sourcefiles table.insert(r, _filter_files(files, _source_exts.filecs)) elseif args.filenone then -- for custom build steps, e.g. files added by add_files but handled by a custom rule -- collect the source files not natively supported by vsxmake, and exclude the header/extra files -- to avoid duplicate display with the include group (add_extrafiles) local _target = info._targets[target] local excludeexts = {} for _, exts in table.orderpairs(_source_exts) do table.join2(excludeexts, exts) end local excludefiles = hashset.from(table.join(_target.headerfiles or {}, _target.extrafiles or {})) local files = {} for _, file in ipairs(_target.sourcefiles) do if not excludefiles:has(file) then table.insert(files, file) end end table.insert(r, _filter_files(files, nil, excludeexts)) elseif args.incc then local files = table.join(info._targets[target].headerfiles or {}, info._targets[target].extrafiles) table.insert(r, _filter_files(files, nil, {".natvis"})) elseif args.incnatvis then local files = table.join(info._targets[target].headerfiles or {}, info._targets[target].extrafiles) table.insert(r, _filter_files(files, {".natvis"})) end return r end return function(match, opt) if type(match) == "table" then return listconfig(match) end return getprop(match, opt) end end function _trycp(file, target, targetname) targetname = targetname or path.filename(file) local targetfile = path.join(target, targetname) targetfile = vsutils.translate_path(targetfile) if os.isfile(targetfile) then dprint("skipped file %s since the file already exists", path.relative(targetfile)) return end os.cp(file, targetfile) end function _writefileifneeded(file, content) file = vsutils.translate_path(file) if os.isfile(file) and io.readfile(file) == content then dprint("skipped file %s since the file has the same content", path.relative(file)) return end -- we need utf8 with bom encoding for unicode -- @see https://github.com/xmake-io/xmake/issues/1689 io.writefile(file, content, {encoding = "utf8bom"}) end -- save plugin arguments for `plugin.vsxmake.autoupdate` -- @see https://github.com/xmake-io/xmake/issues/1895 function _save_plugin_arguments() local vsxmake_cache = localcache.cache("vsxmake") for _, name in ipairs({"kind", "modes", "archs", "outputdir"}) do vsxmake_cache:set(name, option.get(name)) end vsxmake_cache:save() end -- clear cache function _clear_cache() localcache.clear("detect") localcache.clear("option") localcache.clear("package") localcache.clear("toolchain") -- force recheck localcache.set("config", "recheck", true) localcache.save() end -- make function make(version) if not version then version = tonumber(config.get("vs")) if not version then return function(outputdir) raise("invalid vs version, run `xmake f --vs=20xx`") end end end return function(outputdir) vprint("using project kind vs%d", version) assert(version >= 2010, "vsxmake does not support vs version lower than 2010") -- get info and params local info = getinfo(outputdir, vsinfo(version)) local paramsprovidersln = _buildparams(info) -- write solution file local sln = path.join(info.solution_dir, info.slnfile .. ".sln") _writefileifneeded(sln, render(template_sln, "#([A-Za-z0-9_,%.%*%(%)]+)#", "@([^@]+)@", paramsprovidersln)) -- add solution custom file _trycp(template_props, info.vcxproj_rootdir) _trycp(template_targets, info.vcxproj_rootdir) for _, target in ipairs(info.targets) do local paramsprovidertarget = _buildparams(info, target, "") local _target = info._targets[target] local vcxproj_dir = _target.vcxprojdir local projkind = _target.projkind or "cxx" local projext = _target.projext or "vcxproj" -- write project file local proj_file = path.join(vcxproj_dir, target .. "." .. projext) local proj_template = (projkind == "csharp") and template_csproj or template_vcx _writefileifneeded(proj_file, render(proj_template, "#([A-Za-z0-9_,%.%*%(%)]+)#", "@([^@]+)@", paramsprovidertarget)) -- .csproj does not use a .filters sidecar (legacy .vcxproj-only mechanism) if projkind ~= "csharp" then local vcxproj_filters = path.join(vcxproj_dir, target .. ".vcxproj.filters") _writefileifneeded(vcxproj_filters, render(template_fil, "#([A-Za-z0-9_,%.%*%(%)]+)#", "@([^@]+)@", paramsprovidertarget)) end -- add project custom file _trycp(template_props, vcxproj_dir) _trycp(template_targets, vcxproj_dir) _trycp(template_items, vcxproj_dir) _trycp(template_itemfil, vcxproj_dir) end -- clear config and local cache _clear_cache() -- save plugin arguments for autoupdate _save_plugin_arguments() end end