-- -- vs2005_dotnetbase.lua -- Generate a Visual Studio 2005+ .NET project. -- Copyright (c) Jason Perkins and the Premake project -- local p = premake p.vstudio.dotnetbase = {} local vstudio = p.vstudio local dotnetbase = p.vstudio.dotnetbase local project = p.project local config = p.config local fileconfig = p.fileconfig local dotnet = p.tools.dotnet dotnetbase.elements = {} dotnetbase.langObj = {} dotnetbase.netcore = {} -- -- Generate a Visual Studio 200x dotnet project, with support for the new platforms API. -- function dotnetbase.prepare(langObj) dotnetbase.elements.project = langObj.elements.project dotnetbase.elements.projectProperties = langObj.elements.projectProperties dotnetbase.elements.configuration = langObj.elements.configuration dotnetbase.langObj = langObj end function dotnetbase.generate(prj) p.utf8() p.callArray(dotnetbase.elements.project, prj) _p(1,'') dotnetbase.files(prj) _p(1,'') dotnetbase.projectReferences(prj) dotnetbase.packageReferences(prj) dotnetbase.langObj.targets(prj) dotnetbase.buildEvents(prj) p.out('') end -- -- Write the opening element. -- function dotnetbase.projectElement(prj) if dotnetbase.isNewFormatProject(prj) then if prj.flags.WPF then _p('') else _p('') end else local ver = '' local action = p.action.current() if action.vstudio.toolsVersion then ver = string.format(' ToolsVersion="%s"', action.vstudio.toolsVersion) end _p('', ver) end end -- -- Write the opening PropertyGroup, which contains the project-level settings. -- function dotnetbase.projectProperties(prj) _p(1,'') local cfg = project.getfirstconfig(prj) p.callArray(dotnetbase.elements.projectProperties, cfg) _p(1,'') end -- -- Write out the settings for the project configurations. -- function dotnetbase.configurations(prj) for cfg in project.eachconfig(prj) do dotnetbase.configuration(cfg) end end function dotnetbase.configuration(cfg) p.callArray(dotnetbase.elements.configuration, cfg) _p(1,'') end function dotnetbase.dofile(node, cfg, condition) local filecfg = fileconfig.getconfig(node, cfg) if filecfg then local fname = path.translate(node.relpath) -- Files that live outside of the project tree need to be "linked" -- and provided with a project relative pseudo-path. Check for any -- leading "../" sequences and, if found, remove them and mark this -- path as external. local link, count = node.relpath:gsub("%.%.%/", "") local external = (count > 0) -- Try to provide a little bit of flexibility by allowing virtual -- paths for external files. Would be great to support them for all -- files but Visual Studio chokes if file is already in project area. if external and node.vpath ~= node.relpath then link = node.vpath end -- Deduce what, if any, special attributes are required for this file. -- For example, forms may have related source, designer, and resource -- files which need to be associated. local info = dotnet.fileinfo(filecfg) -- Process any sub-elements required by this file; choose the write -- element form to use based on the results. local contents = p.capture(function () -- Try to write file-level elements in the same order as Visual Studio local elements = { "AutoGen", "CopyToOutputDirectory", "DesignTime", "DependentUpon", "DesignTimeSharedInput", "Generator", "LastGenOutput", "SubType", } for _, el in ipairs(elements) do local value = info[el] if value then _p(3,"<%s>%s", el, value, el) end end if info.action == "EmbeddedResource" and cfg.customtoolnamespace then _p(3,"%s", cfg.customtoolnamespace) end end) if #contents > 0 or external then _p(2,'<%s%s Include="%s">', info.action, condition, fname) if external and info.action ~= "EmbeddedResource" then _p(3,'%s', path.translate(link)) end if #contents > 0 then _p("%s", contents) end _p(2,'', info.action) else _p(2,'<%s%s Include="%s" />', info.action, condition, fname) end end end -- -- Write out the source files item group. -- function dotnetbase.files(prj) local firstcfg = project.getfirstconfig(prj) local processfcfg = function(node) -- test if all fileinfo's are going to be the same for each config. local allsame = true local first = nil for cfg in project.eachconfig(prj) do local filecfg = fileconfig.getconfig(node, cfg) local info = dotnet.fileinfo(filecfg) if first == nil then first = info elseif not table.equals(first, info) then allsame = false end end -- output to proj file. if allsame then dotnetbase.dofile(node, firstcfg, '') else for cfg in project.eachconfig(prj) do dotnetbase.dofile(node, cfg, ' ' .. dotnetbase.condition(cfg)) end end end if project.isfsharp(prj) then sorter = function(a,b) verbosef('Sorting F# proj file (%s, %s), index %s < %s', a.name, b.name, a.order, b.order) return a.order < b.order end table.sort(prj._.files, sorter) table.foreachi(prj._.files, processfcfg) else local tr = project.getsourcetree(prj) p.tree.traverse(tr, { onleaf = processfcfg }, false) end end -- -- Write out pre- and post-build events, if provided. -- function dotnetbase.buildEvents(prj) local function output(name, steps) if #steps > 0 then steps = os.translateCommandsAndPaths(steps, prj.basedir, prj.location) steps = table.implode(steps, "", "", "\r\n") _x(2,'<%sBuildEvent>%s', name, steps, name) end end local cfg = project.getfirstconfig(prj) if #cfg.prebuildcommands > 0 or #cfg.postbuildcommands > 0 then _p(1,'') output("Pre", cfg.prebuildcommands) output("Post", cfg.postbuildcommands) _p(1,'') end end -- -- Write the compiler flags for a particular configuration. -- function dotnetbase.compilerProps(cfg) _x(2,'%s', table.concat(cfg.defines, ";")) _p(2,'prompt') _p(2,'4') if not dotnetbase.isNewFormatProject(cfg) then dotnetbase.allowUnsafeBlocks(cfg) end if cfg.flags.FatalCompileWarnings then _p(2,'true') end dotnetbase.debugCommandParameters(cfg) end -- -- Write out the debug start parameters for MonoDevelop/Xamarin Studio. -- function dotnetbase.debugCommandParameters(cfg) if #cfg.debugargs > 0 then _x(2,'%s', table.concat(cfg.debugargs, " ")) end end -- -- Write out the debugging and optimization flags for a configuration. -- function dotnetbase.debugProps(cfg) if _ACTION >= "vs2019" then if cfg.symbols == "Full" then _p(2,'full') _p(2,'true') elseif cfg.symbols == p.OFF then _p(2,'none') _p(2,'false') elseif cfg.symbols == p.ON or cfg.symbols == "FastLink" then _p(2,'pdbonly') _p(2,'true') else _p(2,'portable') _p(2,'true') end else if cfg.symbols == p.ON then _p(2,'true') _p(2,'full') else _p(2,'pdbonly') end end _p(2,'%s', iif(config.isOptimizedBuild(cfg), "true", "false")) end -- -- Write out the target and intermediates settings for a configuration. -- function dotnetbase.outputProps(cfg) local outdir = vstudio.path(cfg, cfg.buildtarget.directory) _x(2,'%s\\', outdir) -- Want to set BaseIntermediateOutputPath because otherwise VS will create obj/ -- anyway. But VS2008 throws up ominous warning if present. local objdir = vstudio.path(cfg, cfg.objdir) if _ACTION > "vs2008" and not dotnetbase.isNewFormatProject(cfg) then _x(2,'%s\\', objdir) _p(2,'$(BaseIntermediateOutputPath)') else _x(2,'%s\\', objdir) end end -- -- Write out the references item group. -- dotnetbase.elements.references = function(cfg) return { dotnetbase.assemblyReferences, dotnetbase.nuGetReferences, } end function dotnetbase.references(prj) for cfg in project.eachconfig(prj) do _p(1,'', dotnetbase.condition(cfg)) p.callArray(dotnetbase.elements.references, cfg) _p(1,'') end end -- -- Write the list of assembly (system, or non-sibling) references. -- function dotnetbase.assemblyReferences(cfg) local prj = cfg.project config.getlinks(cfg, "system", function(original, decorated) local name = path.getname(decorated) if path.getextension(name) == ".dll" then name = name.sub(name, 1, -5) end if decorated:find("/", nil, true) then _x(2,'', name) local decPath, decVars = decorated:match("(.-),") if not decPath then decPath = decorated end _x(3,'%s', path.appendextension(path.translate(decPath), ".dll")) if not config.isCopyLocal(prj, original, true) then _p(3,"False") end _p(2,'') else _x(2,'', name) end end) end -- -- This is a bit janky. To compare versions, we extract all numbers from the -- given string and right-pad the result with zeros. Then we can just do a -- lexicographical compare on the resulting strings. -- -- This is so that we can compare version strings such as "4.6" and "net451" -- with each other. -- function dotnetbase.makeVersionComparable(version) local numbers = "" for number in version:gmatch("%d") do numbers = numbers .. number end return string.format("%-10d", numbers):gsub(" ", "0") end -- -- https://github.com/NuGet/NuGet.Client/blob/dev/test/NuGet.Core.Tests/NuGet.Frameworks.Test/NuGetFrameworkParseTests.cs -- function dotnetbase.frameworkVersionForFolder(folder) -- If this exporter ever supports frameworks such as "netstandard1.3", -- "sl4", "sl5", "uap10", "wp8" or "wp71", this code will need changing -- to match the right folders, depending on the current framework. -- Right now this only matches folders for the .NET Framework. if folder:match("^net%d+$") or folder:match("^[0-9%.]+$") then return dotnetbase.makeVersionComparable(folder) elseif folder == "net" then return dotnetbase.makeVersionComparable("0") end end -- -- Write the list of NuGet references. -- function dotnetbase.nuGetReferences(cfg) local prj = cfg.project if _ACTION >= "vs2010" and not vstudio.nuget2010.supportsPackageReferences(prj) then for _, package in ipairs(prj.nuget) do local id = vstudio.nuget2010.packageId(package) local packageAPIInfo = vstudio.nuget2010.packageAPIInfo(prj, package) local action = p.action.current() local targetFramework = cfg.dotnetframework or action.vstudio.targetFramework local targetVersion = dotnetbase.makeVersionComparable(targetFramework) -- Figure out what folder contains the files for the nearest -- supported .NET Framework version. local files = {} local bestVersion, bestFolder for _, file in ipairs(packageAPIInfo.packageEntries) do local folder = file:match("^lib[\\/](.+)[\\/]") if folder and path.hasextension(file, ".dll") then local version = dotnetbase.frameworkVersionForFolder(folder) if version then files[folder] = files[folder] or {} table.insert(files[folder], file) if version <= targetVersion and (not bestVersion or version > bestVersion) then bestVersion = version bestFolder = folder end end end end if not bestVersion then p.error("NuGet package '%s' is not compatible with project '%s' .NET Framework version '%s'", id, prj.name, targetFramework) end -- Now, add references for all DLLs in that folder. for _, file in ipairs(files[bestFolder]) do -- There's some stuff missing from this include that we -- can't get from the API and would need to download and -- extract the package to figure out. It looks like we can -- just omit it though. -- -- So, for example, instead of: -- -- -- -- We're just outputting: -- -- _x(2, '', path.getbasename(file)) _x(3, '%s', vstudio.path(prj, p.filename(prj.workspace, string.format("packages\\%s.%s\\%s", id, packageAPIInfo.verbatimVersion or packageAPIInfo.version, file)))) if config.isCopyLocal(prj, package, true) then _p(3, 'True') else _p(3, 'False') end _p(2, '') end end end end -- -- Write the list of project dependencies. -- function dotnetbase.projectReferences(prj) if not dotnetbase.isNewFormatProject(prj) then _p(1,'') end local deps = project.getdependencies(prj, 'linkOnly') if #deps > 0 then if dotnetbase.isNewFormatProject(prj) then _p(1,'') end for _, dep in ipairs(deps) do local relpath = vstudio.path(prj, vstudio.projectfile(dep)) _x(2,'', relpath) _p(3,'{%s}', dep.uuid) _x(3,'%s', dep.name) if not config.isCopyLocal(prj, dep.name, true) then _p(3,"False") end _p(2,'') end if dotnetbase.isNewFormatProject(prj) then _p(1,'') end end if not dotnetbase.isNewFormatProject(prj) then _p(1,'') end end -- -- Write the list of package dependencies. -- function dotnetbase.packageReferences(prj) if vstudio.nuget2010.supportsPackageReferences(prj) then local hasNuget = prj.nuget and #prj.nuget>0 for cfg in project.eachconfig(prj) do if cfg.nuget and #cfg.nuget>0 then hasNuget = true end end if hasNuget then _p(1,'') if prj.nuget and #prj.nuget>0 then for _, package in ipairs(prj.nuget) do _p(2,'', vstudio.nuget2010.packageId(package), vstudio.nuget2010.packageVersion(package)) end end for cfg in project.eachconfig(prj) do if cfg.nuget and #cfg.nuget>0 then for _, package in ipairs(cfg.nuget) do if prj.nuget[package]==nil then _p(2,'', vstudio.nuget2010.packageId(package), vstudio.nuget2010.packageVersion(package), dotnetbase.condition(cfg)) end end end end _p(1,'') end end end -- -- Return the Visual Studio architecture identification string. The logic -- to select this is getting more complicated in VS2010, but I haven't -- tackled all the permutations yet. -- function dotnetbase.arch(cfg) local arch = vstudio.archFromConfig(cfg) if arch == "Any CPU" then arch = "AnyCPU" end return arch end -- -- Write the PropertyGroup element for a specific configuration block. -- function dotnetbase.propertyGroup(cfg) p.push('', dotnetbase.condition(cfg)) local arch = dotnetbase.arch(cfg) if arch ~= "AnyCPU" or _ACTION > "vs2008" then p.x('%s', arch) end end -- -- Generators for individual project elements. -- function dotnetbase.applicationIcon(prj) if prj.icon then local icon = vstudio.path(prj, prj.icon) _p(1,'') _x(2,'%s', icon) _p(1,'') end end --------------------------------------------------------------------------- -- -- Support functions -- --------------------------------------------------------------------------- -- -- Format and return a Visual Studio Condition attribute. -- function dotnetbase.condition(cfg) local platform = vstudio.projectPlatform(cfg) local arch = dotnetbase.arch(cfg) return string.format('Condition=" \'$(Configuration)|$(Platform)\' == \'%s|%s\' "', platform, arch) end -- -- When given a .NET Framework version, returns it formatted for NuGet. -- function dotnetbase.formatNuGetFrameworkVersion(framework) return "net" .. framework:gsub("%.", "") end --------------------------------------------------------------------------- -- -- Handlers for individual project elements -- --------------------------------------------------------------------------- function dotnetbase.appDesignerFolder(cfg) _p(2,'Properties') end function dotnetbase.assemblyName(cfg) if not dotnetbase.isNewFormatProject(cfg) --[[or cfg.assemblyname]] then _p(2,'%s', cfg.buildtarget.basename) end end function dotnetbase.commonProperties(prj) if _ACTION > "vs2010" then _p(1,'') end end function dotnetbase.configurationCondition(cfg) _x(2,'%s', cfg.buildcfg) end function dotnetbase.fileAlignment(cfg) if _ACTION >= "vs2010" and not dotnetbase.isNewFormatProject(cfg) then _p(2,'512') end end function dotnetbase.bindingRedirects(cfg) if _ACTION >= "vs2015" and not dotnetbase.isNewFormatProject(cfg) then _p(2, 'true') end end function dotnetbase.outputType(cfg) _p(2,'%s', dotnet.getkind(cfg)) end function dotnetbase.platformCondition(cfg) _p(2,'%s', dotnetbase.arch(cfg.project)) end function dotnetbase.productVersion(cfg) local action = p.action.current() if action.vstudio.productVersion then _p(2,'%s', action.vstudio.productVersion) end end function dotnetbase.projectGuid(cfg) _p(2,'{%s}', cfg.uuid) end function dotnetbase.projectTypeGuids(cfg) if cfg.flags.WPF then _p(2,'{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') end end function dotnetbase.rootNamespace(cfg) if not dotnetbase.isNewFormatProject(cfg) or cfg.namespace then _p(2,'%s', cfg.namespace or cfg.buildtarget.basename) end end function dotnetbase.schemaVersion(cfg) local action = p.action.current() if action.vstudio.csprojSchemaVersion then _p(2,'%s', action.vstudio.csprojSchemaVersion) end end function dotnetbase.NoWarn(cfg) if #cfg.disablewarnings > 0 then local warnings = table.concat(cfg.disablewarnings, ";") _p(2,'%s', warnings) end end function dotnetbase.targetFrameworkVersion(cfg) local action = p.action.current() local framework = cfg.dotnetframework or action.vstudio.targetFramework if framework and not dotnetbase.isNewFormatProject(cfg) then _p(2,'v%s', framework) end end function dotnetbase.csversion(cfg) if cfg.csversion then _p(2,'%s', cfg.csversion) end end function dotnetbase.targetFrameworkProfile(cfg) if _ACTION == "vs2010" then _p(2,'') _p(2,'') end end function dotnetbase.xmlDeclaration() if _ACTION > "vs2008" then p.xmlUtf8() end end function dotnetbase.isNewFormatProject(cfg) local framework = cfg.dotnetframework if not framework then return false end if framework:find('^net') ~= nil then return true end return false end function dotnetbase.netcore.targetFramework(cfg) local action = p.action.current() local framework = cfg.dotnetframework or action.vstudio.targetFramework if framework and dotnetbase.isNewFormatProject(cfg) then _p(2,'%s', framework) end end function dotnetbase.netcore.enableDefaultCompileItems(cfg) _p(2,'%s', iif(cfg.enableDefaultCompileItems, "true", "false")) end function dotnetbase.netcore.useWpf(cfg) if cfg.flags.WPF then _p(2,'true') end end function dotnetbase.allowUnsafeBlocks(cfg) if cfg.clr == "Unsafe" then _p(2,'true') end end