premake / premake-core

Premake
https://premake.github.io/
BSD 3-Clause "New" or "Revised" License
3.22k stars 620 forks source link

NuGet support for Visual Studio projects #197

Closed ghost closed 8 years ago

ghost commented 9 years ago

NuGet is the package manager included in the recent versions of Visual Studio. The repository contains library binaries for at least C# and C++ projects.

Right now if a Premake project is dependent on NuGet packages, the generated project files will need to be manually patched.

I would be willing to create a pull request to add support for this, but I'm unsure as to how it should work.

Should we add a nugetlinks function? Or should we add some kind of syntax for the existing links function, such as a "nuget:" prefix? Or should we do this at all? I'd like to know what you think.

ghost commented 9 years ago

Here's an example of how this would affect the generated project files for vs2015. This adds a dependency to the sdl2 and sdl2.redist NuGet packages:

--- polygonist-shared.vcxproj   2015-08-02 18:38:26 +0300
+++ polygonist-shared.vcxproj   2015-08-02 18:38:35 +0300
@@ -729,7 +729,29 @@
       <Project>{3096E6B0-9C00-9B27-E53E-4CCD51E8787C}</Project>
     </ProjectReference>
   </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
+    <Import Project="packages\sdl2.redist.2.0.3\build\native\sdl2.redist.targets" Condition="Exists('packages\sdl2.redist.2.0.3\build\native\sdl2.redist.targets')" />
+    <Import Project="packages\sdl2.2.0.3\build\native\sdl2.targets" Condition="Exists('packages\sdl2.2.0.3\build\native\sdl2.targets')" />
   </ImportGroup>
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('packages\sdl2.redist.2.0.3\build\native\sdl2.redist.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\sdl2.redist.2.0.3\build\native\sdl2.redist.targets'))" />
+    <Error Condition="!Exists('packages\sdl2.2.0.3\build\native\sdl2.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\sdl2.2.0.3\build\native\sdl2.targets'))" />
+  </Target>
 </Project>

Premake would also need to generate a packages.config file that looks like this:

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="sdl2" version="2.0.3" targetFramework="native" />
  <package id="sdl2.redist" version="2.0.3" targetFramework="native" />
</packages>
tvandijck commented 9 years ago

Probably the easiest and most flexible way is to make a "nuget" module. With a couple of well targeted overrides on the vcxproj backend, you can inject additional output into the vcxproj pretty easily.

And the syntax on how to get a nuget package... I would do something like

project 'foo'
      nuget { 'sdl2' }

as for the 'package.config' file, I would override the "solution.generate" in the action, and then output the additional file there. You can enumerate all the projects of the solution and collect all the nuget packages in a table, and output that...

all it probably takes is:


    premake.api.register {
        name = "nuget",
        scope = "project",
        kind = "list:string",
        tokens = true
    }

       premake.override(premake.vc2010, "importExtensionTargets", function(base, prj)
        p.w('<Import Project="$(VCTargetsPath)\\Microsoft.Cpp.targets" />')
        p.push('<ImportGroup Label="ExtensionTargets">')

        for i = 1, #prj.rules do
            local rule = p.global.getRule(prj.rules[i])
            local loc = vstudio.path(prj, p.filename(rule, ".targets"))
            p.x('<Import Project="%s" />', loc)
        end

                if (prj.nuget) then
                for i = 1, #prj.nuget do
            p.x('<Import Project="%s" />', FormatNuget(prj.nuget[i]))
                    end
                end

        p.pop('</ImportGroup>')
      end)

and then a couple more overrides to add the other pieces in the right spots...
ghost commented 9 years ago

That sounds like the best option. I'll look into implementing this once I have a bit of free time.

TurkeyMan commented 9 years ago

@aleksijuvani Looking forward to this!

tritao commented 9 years ago

Me too, I have needed this before but just ended up working around it.

ghost commented 9 years ago

I've finished the initial implementation. It can be found at aleksijuvani/premake-nuget. I've tested it briefly with one of my own projects. At the moment it only supports C++ projects. I'll need to add support for C# as well.

I'm not quite happy with how I have to handle importExtensionTargets. I have to repeat some code from the original function in there.

Any thoughts?

ghost commented 9 years ago

I've just finished the initial implementation of C# support as well. As far as I can tell, it works, but I'd appreciate it if someone else could also take a look at it.

There is one edge case that I'm not entirely sure how it should be handled. If a package doesn't support the target framework version, should the assembly reference HintPath be set to the nearest supported version? I'm going to do some more testing and see how Visual Studio handles this.

tvandijck commented 9 years ago

This is really nice ;) and yeah, I noticed the importExtensionTargets 'problem' too... I don't think it is too big of a problem, although it would be nice if we had a somewhat less destructive hook into there.

right now, if you overload it, and don't output the rules you essentially break things..

@starkos any thoughts on that? I wonder if we can just make an empty method in there that we can overload... There is other 'extension targets' that could be added as well, so this is not really unique to nuget...

starkos commented 9 years ago

Empty methods for overloading are totally fine. IIRC I don't think I've even finished splitting up the C# exporter anyway, so any movement in that direction is good.

tritao commented 9 years ago

Nice work, @aleksijuvani. This looks great.

I think Premake should support NuGet by default for convenience though. NuGet is very popular and a lot of .NET project will need this.

@starkos: Can this module be added to Premake's default?

ghost commented 9 years ago

I still need to change the module to use call arrays for importExtensionTargets – but other than that, I think it's pretty much done. I'd be all for including the module with Premake if @starkos is OK with it.

starkos commented 9 years ago

I'm OK with it. There is a page in the module development section of the wiki docs on steps to take to make your module ready to embed (which you may or may not have to do for this module, but take a look to be sure).

ghost commented 8 years ago

Status update! I've been completely swamped with work so I haven't had time to finish this, but I'm still interested in getting this done eventually.

tritao commented 8 years ago

Hey @aleksijuvani, no pressure, just a friendly reminder that this would be awesome to get finished and into Premake!

ghost commented 8 years ago

Hey @aleksijuvani, no pressure, just a friendly reminder that this would be awesome to get finished and into Premake!

I know, I have a personal project that could really use this. I'll try to take a look at this next month when I have some more time.

ghost commented 8 years ago

I've opened pull request #445 for this.