vs2010_nuget.lua 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. --
  2. -- vs2010_nuget.lua
  3. -- Generate a NuGet packages.config file.
  4. -- Copyright (c) Jason Perkins and the Premake project
  5. --
  6. local p = premake
  7. p.vstudio.nuget2010 = {}
  8. local vstudio = p.vstudio
  9. local nuget2010 = p.vstudio.nuget2010
  10. local dn2005 = p.vstudio.dotnetbase
  11. local packageAPIInfos = {}
  12. local packageSourceInfos = {}
  13. --
  14. -- These functions take the package string as an argument and give you
  15. -- information about it.
  16. --
  17. function nuget2010.packageId(package)
  18. return package:sub(0, package:find(":") - 1)
  19. end
  20. function nuget2010.packageVersion(package)
  21. return package:sub(package:find(":") + 1, -1)
  22. end
  23. function nuget2010.packageFramework(prj)
  24. if p.project.isdotnet(prj) then
  25. local cfg = p.project.getfirstconfig(prj)
  26. local action = p.action.current()
  27. local framework = cfg.dotnetframework or action.vstudio.targetFramework
  28. return dn2005.formatNuGetFrameworkVersion(framework)
  29. else
  30. return "native"
  31. end
  32. end
  33. function nuget2010.supportsPackageReferences(prj)
  34. return _ACTION >= "vs2017" and p.project.isdotnet(prj)
  35. end
  36. --
  37. -- Given a package string, returns a table containing "verbatimVersion",
  38. -- "version" and for C# packages, "packageEntries".
  39. --
  40. function nuget2010.packageAPIInfo(prj, package)
  41. local id = nuget2010.packageId(package)
  42. local version = nuget2010.packageVersion(package)
  43. -- It's possible that NuGet already has this package in its cache. In
  44. -- that case we can examine the nuspec file and the file listing
  45. -- locally.
  46. local function examinePackageFromCache()
  47. -- It should be possible to implement this for platforms other than
  48. -- Windows, but we'll need to figure out where the NuGet cache is on
  49. -- these platforms (or if they even have one).
  50. if not os.ishost("windows") then
  51. return
  52. end
  53. local cachePath = path.translate(path.join(os.getenv("userprofile"), ".nuget/packages", id))
  54. if os.isdir(cachePath) then
  55. local packageAPIInfo = {}
  56. printf("Examining cached NuGet package '%s'...", id)
  57. io.flush()
  58. local versionPath = path.translate(path.join(cachePath, version))
  59. local nuspecPath = path.translate(path.join(versionPath, id .. ".nuspec"))
  60. if not os.isfile(nuspecPath) then
  61. return
  62. end
  63. local nuspec = io.readfile(nuspecPath)
  64. if not nuspec then
  65. return
  66. end
  67. packageAPIInfo.verbatimVersion = nuspec:match("<version>(.+)</version>")
  68. packageAPIInfo.version = version
  69. if not packageAPIInfo.verbatimVersion then
  70. return
  71. end
  72. if p.project.isdotnet(prj) then
  73. -- Using the local file listing for "packageEntries" might
  74. -- not exactly match what we would get from the API but this
  75. -- doesn't matter. At the moment of writing, we're only
  76. -- interested in knowing what DLL files the package
  77. -- contains.
  78. packageAPIInfo.packageEntries = {}
  79. for _, file in ipairs(os.matchfiles(path.translate(path.join(versionPath, "**")))) do
  80. local extension = path.getextension(file)
  81. if extension ~= ".nupkg" and extension ~= ".sha512" then
  82. table.insert(packageAPIInfo.packageEntries, path.translate(path.getrelative(versionPath, file)))
  83. end
  84. end
  85. if #packageAPIInfo.packageEntries == 0 then
  86. return
  87. end
  88. if nuspec:match("<frameworkAssemblies>(.+)</frameworkAssemblies>") then
  89. p.warn("NuGet package '%s' may depend on .NET Framework assemblies - package dependencies are currently unimplemented", id)
  90. end
  91. end
  92. if nuspec:match("<dependencies>(.+)</dependencies>") then
  93. p.warn("NuGet package '%s' may depend on other packages - package dependencies are currently unimplemented", id)
  94. end
  95. packageAPIInfos[package] = packageAPIInfo
  96. end
  97. end
  98. if not packageAPIInfos[package] then
  99. examinePackageFromCache()
  100. end
  101. -- If we didn't find the package from the cache, use the NuGet API
  102. -- instead.
  103. if not packageAPIInfos[package] then
  104. if not packageSourceInfos[prj.nugetsource] then
  105. local packageSourceInfo = {}
  106. printf("Examining NuGet package source '%s'...", prj.nugetsource)
  107. io.flush()
  108. local response, err, code = http.get(prj.nugetsource)
  109. if err ~= "OK" then
  110. p.error("NuGet API error (%d)\n%s", code, err)
  111. end
  112. response, err = json.decode(response)
  113. if not response then
  114. p.error("Failed to decode NuGet API response (%s)", err)
  115. end
  116. if not response.resources then
  117. p.error("Failed to understand NuGet API response (no resources in response)", id)
  118. end
  119. local packageDisplayMetadataUriTemplate, catalog
  120. for _, resource in ipairs(response.resources) do
  121. if not resource["@id"] then
  122. p.error("Failed to understand NuGet API response (no resource['@id'])")
  123. end
  124. if not resource["@type"] then
  125. p.error("Failed to understand NuGet API response (no resource['@type'])")
  126. end
  127. if resource["@type"]:find("PackageDisplayMetadataUriTemplate") == 1 then
  128. packageDisplayMetadataUriTemplate = resource
  129. end
  130. if resource["@type"]:find("Catalog") == 1 then
  131. catalog = resource
  132. end
  133. end
  134. if not packageDisplayMetadataUriTemplate then
  135. p.error("Failed to understand NuGet API response (no PackageDisplayMetadataUriTemplate resource)")
  136. end
  137. if not catalog then
  138. if prj.nugetsource == "https://api.nuget.org/v3/index.json" then
  139. p.error("Failed to understand NuGet API response (no Catalog resource)")
  140. else
  141. p.error("Package source is not a NuGet gallery - non-gallery sources are currently unsupported", prj.nugetsource, prj.name)
  142. end
  143. end
  144. packageSourceInfo.packageDisplayMetadataUriTemplate = packageDisplayMetadataUriTemplate
  145. packageSourceInfo.catalog = catalog
  146. packageSourceInfos[prj.nugetsource] = packageSourceInfo
  147. end
  148. local packageAPIInfo = {}
  149. printf("Examining NuGet package '%s'...", id)
  150. io.flush()
  151. local response, err, code = http.get(packageSourceInfos[prj.nugetsource].packageDisplayMetadataUriTemplate["@id"]:gsub("{id%-lower}", id:lower()))
  152. if err ~= "OK" then
  153. if code == 404 then
  154. p.error("NuGet package '%s' for project '%s' couldn't be found in the repository", id, prj.name)
  155. else
  156. p.error("NuGet API error (%d)\n%s", code, err)
  157. end
  158. end
  159. response, err = json.decode(response)
  160. if not response then
  161. p.error("Failed to decode NuGet API response (%s)", err)
  162. end
  163. if not response.items or #response.items == 0 then
  164. p.error("Failed to understand NuGet API response (no pages for package '%s')", id)
  165. end
  166. local items = {}
  167. for _, page in ipairs(response.items) do
  168. if not page.items or #page.items == 0 then
  169. p.error("Failed to understand NuGet API response (got a page with no items for package '%s')", id)
  170. end
  171. for _, item in ipairs(page.items) do
  172. table.insert(items, item)
  173. end
  174. end
  175. local versions = {}
  176. for _, item in ipairs(items) do
  177. if not item.catalogEntry then
  178. p.error("Failed to understand NuGet API response (subitem of package '%s' has no catalogEntry)", id)
  179. end
  180. if not item.catalogEntry.version then
  181. p.error("Failed to understand NuGet API response (subitem of package '%s' has no catalogEntry.version)", id)
  182. end
  183. if not item.catalogEntry["@id"] then
  184. p.error("Failed to understand NuGet API response (subitem of package '%s' has no catalogEntry['@id'])", id)
  185. end
  186. table.insert(versions, item.catalogEntry.version)
  187. end
  188. if not table.contains(versions, version) then
  189. local options = table.translate(versions, function(value) return "'" .. value .. "'" end)
  190. options = table.concat(options, ", ")
  191. p.error("'%s' is not a valid version for NuGet package '%s' (options are: %s)", version, id, options)
  192. end
  193. for _, item in ipairs(items) do
  194. if item.catalogEntry.version == version then
  195. local response, err, code = http.get(item.catalogEntry["@id"])
  196. if err ~= "OK" then
  197. if code == 404 then
  198. p.error("NuGet package '%s' version '%s' couldn't be found in the repository even though the API reported that it exists", id, version)
  199. else
  200. p.error("NuGet API error (%d)\n%s", code, err)
  201. end
  202. end
  203. response, err = json.decode(response)
  204. if not response then
  205. p.error("Failed to decode NuGet API response (%s)", err)
  206. end
  207. if not response.verbatimVersion and not response.version then
  208. p.error("Failed to understand NuGet API response (package '%s' version '%s' has no verbatimVersion or version)", id, version)
  209. end
  210. packageAPIInfo.verbatimVersion = response.verbatimVersion
  211. packageAPIInfo.version = response.version
  212. -- C++ packages don't have this, but C# packages have a
  213. -- packageEntries field that lists all the files in the
  214. -- package. We need to look at this to figure out what
  215. -- DLLs to reference in the project file.
  216. if prj.language == "C#" and not response.packageEntries then
  217. p.error("NuGet package '%s' version '%s' has no file listing. This package might be too old to be using this API or it might be a C++ package instead of a .NET Framework package.", id, response.version)
  218. end
  219. if prj.language == "C#" then
  220. packageAPIInfo.packageEntries = {}
  221. for _, item in ipairs(response.packageEntries) do
  222. if not item.fullName then
  223. p.error("Failed to understand NuGet API response (package '%s' version '%s' packageEntry has no fullName)", id, version)
  224. end
  225. table.insert(packageAPIInfo.packageEntries, path.translate(item.fullName))
  226. end
  227. if #packageAPIInfo.packageEntries == 0 then
  228. p.error("NuGet package '%s' file listing is empty", id)
  229. end
  230. if response.frameworkAssemblyGroup then
  231. p.warn("NuGet package '%s' may depend on .NET Framework assemblies - package dependencies are currently unimplemented", id)
  232. end
  233. end
  234. if response.dependencyGroups then
  235. p.warn("NuGet package '%s' may depend on other packages - package dependencies are currently unimplemented", id)
  236. end
  237. break
  238. end
  239. end
  240. packageAPIInfos[package] = packageAPIInfo
  241. end
  242. return packageAPIInfos[package]
  243. end
  244. --
  245. -- Generates the packages.config file.
  246. --
  247. function nuget2010.generatePackagesConfig(prj)
  248. if #prj.nuget > 0 then
  249. p.w('<?xml version="1.0" encoding="utf-8"?>')
  250. p.push('<packages>')
  251. for _, package in ipairs(prj.nuget) do
  252. p.x('<package id="%s" version="%s" targetFramework="%s" />', nuget2010.packageId(package), nuget2010.packageVersion(package), nuget2010.packageFramework(prj))
  253. end
  254. p.pop('</packages>')
  255. end
  256. end
  257. --
  258. -- Generates the NuGet.Config file.
  259. --
  260. function nuget2010.generateNuGetConfig(prj)
  261. if #prj.nuget == 0 then
  262. return
  263. end
  264. if prj.nugetsource == "https://api.nuget.org/v3/index.json" then
  265. return
  266. end
  267. p.w('<?xml version="1.0" encoding="utf-8"?>')
  268. p.push('<configuration>')
  269. p.push('<packageSources>')
  270. -- By specifying "<clear />", we ensure that only the source that we
  271. -- define below is used. Otherwise it would just get added to the list
  272. -- of package sources.
  273. p.x('<clear />')
  274. p.x('<add key="%s" value="%s" />', prj.nugetsource, prj.nugetsource)
  275. p.pop('</packageSources>')
  276. p.pop('</configuration>')
  277. end
  278. --
  279. -- nuget workspace validation
  280. --
  281. function nuget2010.uniqueProjectLocationsWithNuGet(wks)
  282. local locations = {}
  283. for prj in p.workspace.eachproject(wks) do
  284. if not nuget2010.supportsPackageReferences(prj) then
  285. local function fail()
  286. p.error("projects '%s' and '%s' cannot have the same location when using NuGet with different packages (packages.config conflict)", locations[prj.location].name, prj.name)
  287. end
  288. if locations[prj.location] and #locations[prj.location].nuget > 0 and #prj.nuget > 0 then
  289. if #locations[prj.location].nuget ~= #prj.nuget then
  290. fail()
  291. end
  292. for i, package in ipairs(locations[prj.location].nuget) do
  293. if prj.nuget[i] ~= package then
  294. fail()
  295. end
  296. end
  297. end
  298. locations[prj.location] = prj
  299. end
  300. end
  301. end
  302. p.override(p.validation.elements, "workspace", function (oldfn, wks)
  303. local t = oldfn(wks)
  304. table.insert(t, nuget2010.uniqueProjectLocationsWithNuGet)
  305. return t
  306. end)
  307. --
  308. -- nuget project validation
  309. --
  310. function nuget2010.NuGetHasHTTP(prj)
  311. if not http and #prj.nuget > 0 and not nuget2010.supportsPackageReferences(prj) then
  312. p.error("Premake was compiled with --no-curl, but Curl is required for NuGet support (project '%s' is referencing NuGet packages)", prj.name)
  313. end
  314. end
  315. function nuget2010.NuGetPackageStrings(prj)
  316. for _, package in ipairs(prj.nuget) do
  317. local components = package:explode(":")
  318. if #components ~= 2 or #components[1] == 0 or #components[2] == 0 then
  319. p.error("NuGet package '%s' in project '%s' is invalid - please give packages in the format 'id:version', e.g. 'NUnit:3.6.1'", package, prj.name)
  320. end
  321. end
  322. end
  323. p.override(p.validation.elements, "project", function (oldfn, prj)
  324. local t = oldfn(prj)
  325. table.insert(t, nuget2010.NuGetHasHTTP)
  326. table.insert(t, nuget2010.NuGetPackageStrings)
  327. return t
  328. end)