2016-03-19 16:23:28 +00:00
--
-- vs2010_nuget.lua
-- Generate a NuGet packages.config file.
2017-08-30 18:35:42 +00:00
-- Copyright (c) Jason Perkins and the Premake project
2016-03-19 16:23:28 +00:00
--
local p = premake
2017-04-25 05:44:13 +00:00
p.vstudio . nuget2010 = { }
2016-03-19 16:23:28 +00:00
local vstudio = p.vstudio
local nuget2010 = p.vstudio . nuget2010
2017-08-30 18:35:42 +00:00
local dn2005 = p.vstudio . dotnetbase
2016-03-19 16:23:28 +00:00
2017-04-06 17:28:52 +00:00
local packageAPIInfos = { }
2017-04-09 10:55:14 +00:00
local packageSourceInfos = { }
2016-03-19 16:23:28 +00:00
--
-- These functions take the package string as an argument and give you
-- information about it.
--
function nuget2010 . packageId ( package )
return package : sub ( 0 , package : find ( " : " ) - 1 )
end
function nuget2010 . packageVersion ( package )
return package : sub ( package : find ( " : " ) + 1 , - 1 )
end
2017-04-04 16:17:00 +00:00
function nuget2010 . packageFramework ( prj )
2017-01-31 20:10:40 +00:00
if p.project . isdotnet ( prj ) then
2016-03-19 16:23:28 +00:00
local cfg = p.project . getfirstconfig ( prj )
2017-04-25 05:44:13 +00:00
local action = p.action . current ( )
2016-03-19 16:23:28 +00:00
local framework = cfg.dotnetframework or action.vstudio . targetFramework
2017-08-30 18:35:42 +00:00
return dn2005.formatNuGetFrameworkVersion ( framework )
2017-01-31 20:10:40 +00:00
else
return " native "
2016-03-19 16:23:28 +00:00
end
end
2017-09-21 22:40:25 +00:00
function nuget2010 . supportsPackageReferences ( prj )
return _ACTION >= " vs2017 " and p.project . isdotnet ( prj )
end
2017-08-02 10:26:34 +00:00
--
-- Given a package string, returns a table containing "verbatimVersion",
-- "version" and for C# packages, "packageEntries".
--
2017-04-07 14:25:35 +00:00
function nuget2010 . packageAPIInfo ( prj , package )
2017-08-01 10:57:27 +00:00
local id = nuget2010.packageId ( package )
local version = nuget2010.packageVersion ( package )
2017-04-06 15:47:13 +00:00
2017-08-02 10:26:34 +00:00
-- It's possible that NuGet already has this package in its cache. In
-- that case we can examine the nuspec file and the file listing
-- locally.
2017-04-09 10:55:14 +00:00
2017-08-02 10:26:34 +00:00
local function examinePackageFromCache ( )
-- It should be possible to implement this for platforms other than
-- Windows, but we'll need to figure out where the NuGet cache is on
-- these platforms (or if they even have one).
2017-04-09 10:55:14 +00:00
2017-08-02 10:26:34 +00:00
if not os.ishost ( " windows " ) then
return
2017-04-09 10:55:14 +00:00
end
2017-08-02 10:26:34 +00:00
local cachePath = path.translate ( path.join ( os.getenv ( " userprofile " ) , " .nuget/packages " , id ) )
2017-04-09 10:55:14 +00:00
2017-08-02 10:26:34 +00:00
if os.isdir ( cachePath ) then
local packageAPIInfo = { }
2017-04-09 10:55:14 +00:00
2017-08-02 10:26:34 +00:00
printf ( " Examining cached NuGet package '%s'... " , id )
io.flush ( )
2017-04-09 10:55:14 +00:00
2017-08-02 10:26:34 +00:00
local versionPath = path.translate ( path.join ( cachePath , version ) )
2017-04-09 10:55:14 +00:00
2017-08-02 10:26:34 +00:00
local nuspecPath = path.translate ( path.join ( versionPath , id .. " .nuspec " ) )
2017-04-09 10:55:14 +00:00
2017-08-02 10:26:34 +00:00
if not os.isfile ( nuspecPath ) then
return
2017-04-09 10:55:14 +00:00
end
2017-08-02 10:26:34 +00:00
local nuspec = io.readfile ( nuspecPath )
if not nuspec then
return
2017-04-09 10:55:14 +00:00
end
2017-08-02 10:26:34 +00:00
packageAPIInfo.verbatimVersion = nuspec : match ( " <version>(.+)</version> " )
packageAPIInfo.version = version
if not packageAPIInfo.verbatimVersion then
return
2017-04-09 10:55:14 +00:00
end
2017-08-02 10:26:34 +00:00
if p.project . isdotnet ( prj ) then
-- Using the local file listing for "packageEntries" might
-- not exactly match what we would get from the API but this
-- doesn't matter. At the moment of writing, we're only
-- interested in knowing what DLL files the package
-- contains.
packageAPIInfo.packageEntries = { }
for _ , file in ipairs ( os.matchfiles ( path.translate ( path.join ( versionPath , " ** " ) ) ) ) do
local extension = path.getextension ( file )
if extension ~= " .nupkg " and extension ~= " .sha512 " then
table.insert ( packageAPIInfo.packageEntries , path.translate ( path.getrelative ( versionPath , file ) ) )
end
end
if # packageAPIInfo.packageEntries == 0 then
return
end
2017-04-09 10:55:14 +00:00
2017-08-02 10:26:34 +00:00
if nuspec : match ( " <frameworkAssemblies>(.+)</frameworkAssemblies> " ) then
p.warn ( " NuGet package '%s' may depend on .NET Framework assemblies - package dependencies are currently unimplemented " , id )
end
end
if nuspec : match ( " <dependencies>(.+)</dependencies> " ) then
p.warn ( " NuGet package '%s' may depend on other packages - package dependencies are currently unimplemented " , id )
2017-04-09 10:55:14 +00:00
end
2017-08-02 10:26:34 +00:00
packageAPIInfos [ package ] = packageAPIInfo
end
end
2017-04-09 10:55:14 +00:00
2017-08-02 10:26:34 +00:00
if not packageAPIInfos [ package ] then
examinePackageFromCache ( )
2017-04-09 10:55:14 +00:00
end
2017-08-02 10:26:34 +00:00
-- If we didn't find the package from the cache, use the NuGet API
-- instead.
2017-08-01 10:57:27 +00:00
if not packageAPIInfos [ package ] then
2017-08-02 10:26:34 +00:00
if not packageSourceInfos [ prj.nugetsource ] then
local packageSourceInfo = { }
printf ( " Examining NuGet package source '%s'... " , prj.nugetsource )
io.flush ( )
local response , err , code = http.get ( prj.nugetsource )
if err ~= " OK " then
p.error ( " NuGet API error (%d) \n %s " , code , err )
end
response , err = json.decode ( response )
if not response then
p.error ( " Failed to decode NuGet API response (%s) " , err )
end
if not response.resources then
p.error ( " Failed to understand NuGet API response (no resources in response) " , id )
end
local packageDisplayMetadataUriTemplate , catalog
for _ , resource in ipairs ( response.resources ) do
if not resource [ " @id " ] then
p.error ( " Failed to understand NuGet API response (no resource['@id']) " )
end
if not resource [ " @type " ] then
p.error ( " Failed to understand NuGet API response (no resource['@type']) " )
end
if resource [ " @type " ] : find ( " PackageDisplayMetadataUriTemplate " ) == 1 then
packageDisplayMetadataUriTemplate = resource
end
if resource [ " @type " ] : find ( " Catalog " ) == 1 then
catalog = resource
end
end
if not packageDisplayMetadataUriTemplate then
p.error ( " Failed to understand NuGet API response (no PackageDisplayMetadataUriTemplate resource) " )
end
if not catalog then
if prj.nugetsource == " https://api.nuget.org/v3/index.json " then
p.error ( " Failed to understand NuGet API response (no Catalog resource) " )
else
p.error ( " Package source is not a NuGet gallery - non-gallery sources are currently unsupported " , prj.nugetsource , prj.name )
end
end
packageSourceInfo.packageDisplayMetadataUriTemplate = packageDisplayMetadataUriTemplate
packageSourceInfo.catalog = catalog
packageSourceInfos [ prj.nugetsource ] = packageSourceInfo
end
2017-08-01 10:57:27 +00:00
local packageAPIInfo = { }
2017-04-06 17:28:52 +00:00
2017-08-01 10:57:27 +00:00
printf ( " Examining NuGet package '%s'... " , id )
io.flush ( )
2017-04-06 15:47:13 +00:00
2017-04-09 10:55:14 +00:00
local response , err , code = http.get ( packageSourceInfos [ prj.nugetsource ] . packageDisplayMetadataUriTemplate [ " @id " ] : gsub ( " {id%-lower} " , id : lower ( ) ) )
2017-04-06 15:47:13 +00:00
2017-08-01 10:57:27 +00:00
if err ~= " OK " then
if code == 404 then
p.error ( " NuGet package '%s' for project '%s' couldn't be found in the repository " , id , prj.name )
else
p.error ( " NuGet API error (%d) \n %s " , code , err )
2017-04-06 15:47:13 +00:00
end
2017-08-01 10:57:27 +00:00
end
2017-04-06 17:28:52 +00:00
2017-08-01 10:57:27 +00:00
response , err = json.decode ( response )
2017-04-06 17:28:52 +00:00
2017-08-01 10:57:27 +00:00
if not response then
p.error ( " Failed to decode NuGet API response (%s) " , err )
end
2017-04-06 17:28:52 +00:00
2017-08-01 10:57:27 +00:00
if not response.items or # response.items == 0 then
2017-04-12 10:20:17 +00:00
p.error ( " Failed to understand NuGet API response (no pages for package '%s') " , id )
2017-08-01 10:57:27 +00:00
end
2017-04-06 17:28:52 +00:00
2017-04-12 10:20:17 +00:00
local items = { }
for _ , page in ipairs ( response.items ) do
if not page.items or # page.items == 0 then
p.error ( " Failed to understand NuGet API response (got a page with no items for package '%s') " , id )
2017-04-06 17:28:52 +00:00
end
2017-04-12 10:20:17 +00:00
for _ , item in ipairs ( page.items ) do
table.insert ( items , item )
end
end
2017-04-06 17:28:52 +00:00
2017-08-01 10:57:27 +00:00
local versions = { }
2017-04-06 17:28:52 +00:00
2017-04-12 10:20:17 +00:00
for _ , item in ipairs ( items ) do
2017-08-01 10:57:27 +00:00
if not item.catalogEntry then
p.error ( " Failed to understand NuGet API response (subitem of package '%s' has no catalogEntry) " , id )
end
2017-04-06 17:28:52 +00:00
2017-08-01 10:57:27 +00:00
if not item.catalogEntry . version then
p.error ( " Failed to understand NuGet API response (subitem of package '%s' has no catalogEntry.version) " , id )
end
2017-04-06 17:28:52 +00:00
2017-08-01 10:57:27 +00:00
if not item.catalogEntry [ " @id " ] then
p.error ( " Failed to understand NuGet API response (subitem of package '%s' has no catalogEntry['@id']) " , id )
2017-04-06 17:28:52 +00:00
end
2017-08-01 10:57:27 +00:00
table.insert ( versions , item.catalogEntry . version )
end
2017-04-06 17:28:52 +00:00
2017-08-01 10:57:27 +00:00
if not table.contains ( versions , version ) then
local options = table.translate ( versions , function ( value ) return " ' " .. value .. " ' " end )
options = table.concat ( options , " , " )
p.error ( " '%s' is not a valid version for NuGet package '%s' (options are: %s) " , version , id , options )
end
2017-04-06 17:28:52 +00:00
2017-04-12 10:20:17 +00:00
for _ , item in ipairs ( items ) do
2017-08-01 10:57:27 +00:00
if item.catalogEntry . version == version then
local response , err , code = http.get ( item.catalogEntry [ " @id " ] )
2017-04-06 17:28:52 +00:00
2017-08-01 10:57:27 +00:00
if err ~= " OK " then
if code == 404 then
2017-04-07 14:53:39 +00:00
p.error ( " NuGet package '%s' version '%s' couldn't be found in the repository even though the API reported that it exists " , id , version )
2017-08-01 10:57:27 +00:00
else
p.error ( " NuGet API error (%d) \n %s " , code , err )
2017-04-06 17:28:52 +00:00
end
2017-08-01 10:57:27 +00:00
end
2017-04-06 17:28:52 +00:00
2017-08-01 10:57:27 +00:00
response , err = json.decode ( response )
2017-04-06 17:28:52 +00:00
2017-08-01 10:57:27 +00:00
if not response then
p.error ( " Failed to decode NuGet API response (%s) " , err )
end
2017-04-06 17:28:52 +00:00
2017-08-01 10:57:27 +00:00
if not response.verbatimVersion and not response.version then
p.error ( " Failed to understand NuGet API response (package '%s' version '%s' has no verbatimVersion or version) " , id , version )
end
2017-04-06 17:28:52 +00:00
2017-08-01 10:57:27 +00:00
packageAPIInfo.verbatimVersion = response.verbatimVersion
packageAPIInfo.version = response.version
2017-04-06 17:28:52 +00:00
2017-08-01 10:57:27 +00:00
-- C++ packages don't have this, but C# packages have a
-- packageEntries field that lists all the files in the
-- package. We need to look at this to figure out what
-- DLLs to reference in the project file.
2017-04-07 14:07:17 +00:00
2017-08-01 10:57:27 +00:00
if prj.language == " C# " and not response.packageEntries then
2017-04-07 14:53:39 +00:00
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 )
2017-08-01 10:57:27 +00:00
end
2017-04-07 14:07:17 +00:00
2017-08-01 10:57:27 +00:00
if prj.language == " C# " then
packageAPIInfo.packageEntries = { }
2017-04-07 14:07:17 +00:00
2017-08-01 10:57:27 +00:00
for _ , item in ipairs ( response.packageEntries ) do
if not item.fullName then
p.error ( " Failed to understand NuGet API response (package '%s' version '%s' packageEntry has no fullName) " , id , version )
2017-04-07 14:07:17 +00:00
end
2017-08-01 10:57:27 +00:00
table.insert ( packageAPIInfo.packageEntries , path.translate ( item.fullName ) )
end
if # packageAPIInfo.packageEntries == 0 then
p.error ( " NuGet package '%s' file listing is empty " , id )
end
2017-04-13 11:03:54 +00:00
if response.frameworkAssemblyGroup then
p.warn ( " NuGet package '%s' may depend on .NET Framework assemblies - package dependencies are currently unimplemented " , id )
end
end
if response.dependencyGroups then
p.warn ( " NuGet package '%s' may depend on other packages - package dependencies are currently unimplemented " , id )
2017-04-06 17:28:52 +00:00
end
2017-08-01 10:57:27 +00:00
break
end
2017-04-06 15:47:13 +00:00
end
2017-04-07 14:25:35 +00:00
2017-08-01 10:57:27 +00:00
packageAPIInfos [ package ] = packageAPIInfo
end
2017-04-07 14:25:35 +00:00
return packageAPIInfos [ package ]
2017-04-06 15:47:13 +00:00
end
2016-03-19 16:23:28 +00:00
--
-- Generates the packages.config file.
--
2017-04-04 16:17:00 +00:00
function nuget2010 . generatePackagesConfig ( prj )
if # prj.nuget > 0 then
2017-08-01 10:57:27 +00:00
p.w ( ' <?xml version="1.0" encoding="utf-8"?> ' )
p.push ( ' <packages> ' )
2016-03-19 16:23:28 +00:00
2017-04-04 16:17:00 +00:00
for _ , package in ipairs ( prj.nuget ) do
p.x ( ' <package id="%s" version="%s" targetFramework="%s" /> ' , nuget2010.packageId ( package ) , nuget2010.packageVersion ( package ) , nuget2010.packageFramework ( prj ) )
2017-08-01 10:57:27 +00:00
end
2016-03-19 16:23:28 +00:00
2017-08-01 10:57:27 +00:00
p.pop ( ' </packages> ' )
end
2017-04-04 16:17:00 +00:00
end
2017-04-09 10:55:14 +00:00
--
-- Generates the NuGet.Config file.
--
function nuget2010 . generateNuGetConfig ( prj )
if # prj.nuget == 0 then
return
end
if prj.nugetsource == " https://api.nuget.org/v3/index.json " then
return
end
p.w ( ' <?xml version="1.0" encoding="utf-8"?> ' )
p.push ( ' <configuration> ' )
p.push ( ' <packageSources> ' )
-- By specifying "<clear />", we ensure that only the source that we
-- define below is used. Otherwise it would just get added to the list
-- of package sources.
p.x ( ' <clear /> ' )
p.x ( ' <add key="%s" value="%s" /> ' , prj.nugetsource , prj.nugetsource )
p.pop ( ' </packageSources> ' )
p.pop ( ' </configuration> ' )
end
2017-09-21 22:40:25 +00:00
--
-- nuget workspace validation
--
function nuget2010 . uniqueProjectLocationsWithNuGet ( wks )
local locations = { }
for prj in p.workspace . eachproject ( wks ) do
if not nuget2010.supportsPackageReferences ( prj ) then
local function fail ( )
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 )
end
if locations [ prj.location ] and # locations [ prj.location ] . nuget > 0 and # prj.nuget > 0 then
if # locations [ prj.location ] . nuget ~= # prj.nuget then
fail ( )
end
for i , package in ipairs ( locations [ prj.location ] . nuget ) do
if prj.nuget [ i ] ~= package then
fail ( )
end
end
end
locations [ prj.location ] = prj
end
end
end
p.override ( p.validation . elements , " workspace " , function ( oldfn , wks )
local t = oldfn ( wks )
table.insert ( t , nuget2010.uniqueProjectLocationsWithNuGet )
return t
end )
--
-- nuget project validation
--
function nuget2010 . NuGetHasHTTP ( prj )
if not http and # prj.nuget > 0 and not nuget2010.supportsPackageReferences ( prj ) then
p.error ( " Premake was compiled with --no-curl, but Curl is required for NuGet support (project '%s' is referencing NuGet packages) " , prj.name )
end
end
function nuget2010 . NuGetPackageStrings ( prj )
for _ , package in ipairs ( prj.nuget ) do
local components = package : explode ( " : " )
if # components ~= 2 or # components [ 1 ] == 0 or # components [ 2 ] == 0 then
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 )
end
end
end
p.override ( p.validation . elements , " project " , function ( oldfn , prj )
local t = oldfn ( prj )
table.insert ( t , nuget2010.NuGetHasHTTP )
table.insert ( t , nuget2010.NuGetPackageStrings )
return t
end )