Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A simple way to package an app #630

Closed
poma opened this issue Mar 16, 2016 · 28 comments
Closed

A simple way to package an app #630

poma opened this issue Mar 16, 2016 · 28 comments

Comments

@poma
Copy link

poma commented Mar 16, 2016

I'm not sure why documentation recommends things like OctoPack and Auto.Squirrel when packaging can be easily integrated directly into build process using only nuget and squirrel. Just define a build target in .csproj file:

<Target Name="AfterBuild" Condition=" '$(Configuration)' == 'Release'">
  <GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
    <Output TaskParameter="Assemblies" ItemName="myAssemblyInfo"/>
  </GetAssemblyIdentity>
  <Exec Command="nuget pack MyApp.nuspec -Version %(myAssemblyInfo.Version) -Properties Configuration=Release -OutputDirectory $(OutDir) -BasePath $(OutDir)" />
  <Exec Command="squirrel --releasify $(OutDir)MyApp.%(myAssemblyInfo.Version).nupkg" />
</Target>

this will generate a nuget package from .nuspec file setting version from AssemblyInfo.cs and place it in OutDir (by default bin\Release). Then it will generate release files from it.

Here is an example .nuget file for myApp:

<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
  <metadata>
    <id>MyApp</id>
    <!-- version will be replaced by MSBuild -->
    <version>0.0.0.0</version>
    <title>title</title>
    <authors>authors</authors>
    <description>description</description>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <copyright>Copyright 2016</copyright>
    <dependencies />
  </metadata>
  <files>
    <file src="*.*" target="lib\net45\" exclude="*.pdb;*.nupkg;*.vshost.*"/>
  </files>
</package>

Solution needs to have nuget.exe available for example by installing NuGet.CommandLine package. And also it suffers from this bug when sometimes NuGet packages are not loaded properly and throws nuget/squirrel is not recogized (9009) errors.

you may simply need to restart Visual Studio so the Package Manager Console will have loaded all the package tools

Other than that I think it's the simplest solution that fits most simple apps and should be recommended in Getting Started tutorial. I'm not sure whether including nuget.exe in squirrel package is a good practice but it could eliminate an additional dependency on NuGet.CommandLine and make MSBuild integration work out of the box.

kenbailey pushed a commit to kenbailey/Squirrel.Windows that referenced this issue Mar 26, 2016
@kenbailey
Copy link
Contributor

@poma, thanks for the description of the steps. I've created some initial docs you can checkout on my docs-vs-package branch. Feel free to submit a PR based on it or send me comments of any updates and I will submit a PR.

My initial thought is to keep the Package Manager GUI on the getting started and simply provide a quick link to this approach.

Thanks again!

anaisbetts added a commit that referenced this issue Apr 12, 2016
Docs for Visual Studio Packing (Issue #630)
@mbulava
Copy link

mbulava commented Jul 29, 2016

Using this approach I keep getting error:
5> 'squirrel' is not recognized as an internal or external command,
5> operable program or batch file.

I've restarted VS serveral times, and the end goal is to get this to work on my Build server... Any ideas Suggestions?

@HalidCisse
Copy link

it never worked for me too

@poma
Copy link
Author

poma commented Jul 29, 2016

Probably because Visual Studio can't find squirrel.exe or nuget.exe. If you have corresponding NuGet packages installed VS will load paths after you open Package Manager Console window. Otherwise just use full paths to exe files or place exe files where Windows can find them (within PATH variable).

@cburman01
Copy link

@poma This was a great idea. Thanks!

@agostinocinelli
Copy link

agostinocinelli commented Nov 9, 2016

My 2 Cents:
Add

<files>
    <file src="*.*" target="lib\net45\" exclude="*.pdb;*.nupkg;*.vshost.*"/>
    <file src="**\*.*" target="lib\net45\" exclude="*.pdb;*.nupkg;*.vshost.*"/>
  </files>

to include all subfolder recursively.
It would be nice adding it in docs
https://github.com/Squirrel/Squirrel.Windows/blob/master/docs/using/visual-studio-packaging.md

@celeron533
Copy link

I restarted the VS several times but still got nuget 9009 error.
After re-installed the NuGet Package Manager in VS, it works. 👍

@poma
Copy link
Author

poma commented Nov 30, 2016

@celeron533 probably you didn't open Package Manager Console after restarting VS to scan for executable tools. I've made a pull request #830 that clarifies how to avoid that error but it is still pending.

@pdinnissen
Copy link

Version 1.5 breaks this. The Revision needs to be stripped from the Version string:
$([System.Version]::Parse(%(myAssemblyInfo.Version)).ToString(3))
(credit to http://stackoverflow.com/a/32855014)
Or better yet maybe there's a way to use the AssemblyInformationalVersion instead since that's what typically used by nuget by default?

@Yitzchok
Copy link

In order to find squirrel.exe you can use MSBuild Transforms

Example see @(squirrelPackage->'%(fullpath)')

<ItemGroup>
   <SquirrelPackage Include="..\..\packages\squirrel.windows.*\tools\Squirrel.exe" />
</ItemGroup>


<Target Name="AfterBuild" Condition=" '$(Configuration)' == 'Release'">
  <GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
    <Output TaskParameter="Assemblies" ItemName="myAssemblyInfo"/>
  </GetAssemblyIdentity>
  <Exec Command="nuget pack MyApp.nuspec -Version %(myAssemblyInfo.Version) -Properties Configuration=Release -OutputDirectory $(OutDir) -BasePath $(OutDir)" />
  <Exec Command="@(squirrelPackage->'%(fullpath)') --releasify $(OutDir)MyApp.%(myAssemblyInfo.Version).nupkg" />
</Target>

@poma
Copy link
Author

poma commented Mar 21, 2017

Nice! Can you make a pull request?

@agostinocinelli
Copy link

I'm facing another problem:
When I run "squirrel --releasify XYZ.nupkg" in PackageManagerConsole, output files are generated in (SolutionFolder)/Released
When VS runs the same command in the .csproj, the files are generated in (SolutionFolder)/(MainProjectFolder)/Releases

How can i specify output dir? Am I missing smt?

@markfox1
Copy link

@poma's solution, with some additions from the conversation here, very nearly works for me. I'm working in VB, so there are some slight differences, but it basically works. I agree with @agostinocinelli that it seems a little off-putting to have the Releases folder showing up under the project folder and not the solution folder. I'd like to better understand the implications for multi-project solutions.

Anyways, here is what worked for me:

<Target Name="AfterBuild" Condition=" '$(Configuration)' == 'Release'">
    <GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
      <Output TaskParameter="Assemblies" ItemName="myAssemblyInfo"/>
    </GetAssemblyIdentity>
    <Exec command="..\packages\Nuget.CommandLine.3.5.0\tools\nuget pack SquirrelTest.nuspec -Version %(myAssemblyInfo.Version) -Properties Configuration=Release" />
    <Exec Command="..\packages\squirrel.windows.1.5.2\tools\squirrel --releasify SquirrelTest.$([System.Version]::Parse(%(myAssemblyInfo.Version)).ToString(3)).nupkg" />
</Target>

I don't like having the versions of squirrel and Nuget in the Exec, but @Yitzchok's solution didn't work because of spaces in one of the parent directories containing this code.

@markfox1
Copy link

@agostinocinelli, the only thing you are missing to get Squirrel to output to another directory is the documentation for the Squirrel command-line arguments.

@markfox1
Copy link

I've tweaked this to the point where I'm very nearly happy. Spaces aren't a problem in full paths if they are properly quoted with &quot;. Keep in mind that I'm working in VB, but the only difference I'm seeing is one less .. in the paths. This is what I have now:

<ItemGroup>
  <NugetPackage Include="..\packages\Nuget.CommandLine.*\tools\NuGet.exe" />
</ItemGroup>
<ItemGroup>
  <SquirrelPackage Include="..\packages\squirrel.windows.*\tools\Squirrel.exe" />
</ItemGroup>
<Target Name="AfterBuild" Condition=" '$(Configuration)' == 'Release'">
  <GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
    <Output TaskParameter="Assemblies" ItemName="myAssemblyInfo"/>
  </GetAssemblyIdentity>
  <Exec Command="&quot;@(NugetPackage->'%(fullpath)')&quot; pack SquirrelTest.nuspec -Version %(myAssemblyInfo.Version) -Properties Configuration=Release" />
  <Exec Command="&quot;@(SquirrelPackage->'%(fullpath)')&quot; --releasify SquirrelTest.$([System.Version]::Parse(%(myAssemblyInfo.Version)).ToString(3)).nupkg --releaseDir=../Releases " />
</Target>

The only thing I don't like is using the AfterBuild target, which executes after every build, but it is as simple as changing the target to, say, Package, and executing MSBuild from the command-line with Package as the target.

cprcrack added a commit to cprcrack/AdaptiveTaskbar that referenced this issue Apr 28, 2017
@PHeonix25
Copy link
Contributor

PHeonix25 commented May 2, 2017

Hey guys, there was a formatting issue in the docs, which I've fixed with #1012

In addition, I'd like to share what I ended up with (that combines a lot of the solutions above) for the next person that comes along:

  <ItemGroup>
    <NuGetCommandLine Include="..\packages\NuGet.CommandLine.*\tools\nuget.exe">
      <InProject>False</InProject>
    </NuGetCommandLine>
    <Squirrel Include="..\packages\Squirrel.Windows.*\tools\squirrel.exe">
      <InProject>False</InProject>
    </Squirrel>
  </ItemGroup>
  <Target Name="AfterBuild" Condition=" '$(Configuration)' == 'Release'">
    <!-- Add some nice errors for the next person that comes along -->
    <Error Condition="!Exists(@(NuGetCommandLine->'%(FullPath)'))" Text="You are trying to use the Nuget.CommandLine package, but it is not installed. Please install NuGet.CommandLine from the Package Manager." />
    <Error Condition="!Exists(@(Squirrel->'%(FullPath)'))" Text="You are trying to use the Squirrel.Windows package, but it is not installed. Please install Squirrel.Windows from the Package Manager." />
    <GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
      <Output TaskParameter="Assemblies" ItemName="assemblyInfo" />
    </GetAssemblyIdentity>
    <Exec Command="@(NuGetCommandLine->'%(FullPath)') pack MYAPPLICATION.nuspec -Version $([System.Version]::Parse(%(assemblyInfo.Version)).ToString(3)) -Properties Configuration=Release -OutputDirectory $(OutDir) -BasePath $(OutDir)" />
    <Exec Command="@(Squirrel->'%(FullPath)') --releasify $(OutDir)MYAPPLICATION.$([System.Version]::Parse(%(assemblyInfo.Version)).ToString(3)).nupkg --releaseDir=../RELEASES" />
  </Target>

Please note (again, for the next guy) you'll need to replace the two MYAPPLICATION mentions above.

Hope this helps someone.

@poma
Copy link
Author

poma commented May 3, 2017

Why not replace MYAPPLICATION with a macro such as $(TargetName)

@PHeonix25
Copy link
Contributor

@poma - for me (due to the way the project has grown over time), it's because they are different.
For others following along later, that might be a much nicer solution indeed. Thanks.

@markfox1
Copy link

markfox1 commented May 15, 2017

@PHeonix25 You'll want to put &quot;'s around the first part of the Command parameter to the Exec. Like I did above. The ..\RELEASES in the last Exec should be ..\Releases as well.

VS was giving me grief when I changed my existing project file and let it reload. It didn't like the -OutputDirectory and -BasePath arguments, nor the --releasify $(OutDir).... When I closed the project and loaded it up again, all was well.

The error messages are welcome, and already saved us a bunch of fiddling this morning.

@poma
Copy link
Author

poma commented Aug 3, 2017

Created a pull request #1120 that includes all suggestions above, please review

@awbacker
Copy link

Reviewing that PR as well, but its been months so.. in the meantime, this is what I ended up with in order to get a well-enough functioning AfterBuild target in my primary project (My.App here). Builds fine from the solution level with msbuild MyApp.sln /p:..release..

Maybe this would work as a slightly more understandable doc until the PR is in? It certainly helps with the props set like that (esp. releaseDir)

<Target Name="AfterBuild" Condition=" '$(Configuration)' == 'Release'">
    <GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
        <Output TaskParameter="Assemblies" ItemName="myAssemblyInfo"/>
    </GetAssemblyIdentity>
    <PropertyGroup>
        <NuGetExe>..\packages\NuGet.CommandLine.4.3.0\tools\NuGet.exe</NuGetExe>
        <SquirrelExe>..\packages\squirrel.windows.1.7.8\tools\squirrel.exe</SquirrelExe>
        <SemVerNumber>$([System.Version]::Parse(%(myAssemblyInfo.Version)).ToString(3))</SemVerNumber>
        <NuSpecFile>MyApp.nuspec</NuSpecFile>
        <ReleaseDir>..\Releases\</ReleaseDir>
        <!-- extra optional params for squirrel. can be empty -->
        <SquirrelParams>--icon Images\favicon.ico --setupIcon Images\favicon.ico</SquirrelParams>
    </PropertyGroup>
    <!-- build nupkg into the project local bin\Release\ directory temporarily -->
    <Exec Command="$(NuGetExe) pack $(NuSpecFile) -Version $(SemVerNumber) -Properties Configuration=Release -OutputDirectory $(OutDir) -BasePath $(OutDir)" />
    <!-- squirrelify into the release dir (usually at solution level.  change the property above for a different location -->
    <Exec Command="$(SquirrelExe) --no-msi --releasify $(OutDir)MyApp.$(SemVerNumber).nupkg --releaseDir=$(ReleaseDir) $(SquirrelParams)" />
</Target>

SolutionDir\My.App\MyApp.nuspec

- same as example
- uses src="**\*.*" instead of *.* for

@poma
Copy link
Author

poma commented Oct 27, 2017

Your solution looks much cleaner. If we merge it with suggestions above result will look like:

<Target Name="AfterBuild" Condition=" '$(Configuration)' == 'Release'">
    <GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
        <Output TaskParameter="Assemblies" ItemName="myAssemblyInfo"/>
    </GetAssemblyIdentity>
    <PropertyGroup>
        <NuGetExe>..\packages\NuGet.CommandLine.*\tools\nuget.exe</NuGetExe>
        <SquirrelExe>..\packages\Squirrel.Windows.*\tools\squirrel.exe</SquirrelExe>
        <SemVerNumber>$([System.Version]::Parse(%(myAssemblyInfo.Version)).ToString(3))</SemVerNumber>
        <ReleaseDir>..\Releases\</ReleaseDir>
        <!-- extra optional params for squirrel. can be empty -->
        <SquirrelParams>--no-msi</SquirrelParams>
    </PropertyGroup>
    <!-- Add some nice errors for the next person that comes along -->
    <Error Condition="!Exists(@(NuGetExe->'%(FullPath)'))" Text="You are trying to use the NuGet.CommandLine package, but it is not installed. Please install NuGet.CommandLine from the Package Manager." />
    <Error Condition="!Exists(@(SquirrelExe->'%(FullPath)'))" Text="You are trying to use the Squirrel.Windows package, but it is not installed. Please install Squirrel.Windows from the Package Manager." />
    <!-- build nupkg into the project local bin\Release\ directory temporarily -->
    <Exec Command="&quot;@(NuGetExe->'%(FullPath)')&quot; pack $(TargetName).nuspec -Version $(SemVerNumber) -OutputDirectory $(OutDir) -BasePath $(OutDir)" />
    <!-- squirrelify into the release dir (usually at solution level. Change the property above for a different location -->
    <Exec Command="&quot;@(SquirrelExe->'%(FullPath)')&quot; --releasify $(OutDir)MyApp.$(SemVerNumber).nupkg --releaseDir=$(ReleaseDir) $(SquirrelParams)" />
</Target>

And there is also question about @(NuGetExe->'%(FullPath)') vs %(NuGetExe.FullPath) as suggested in a pull request. I've just taken my expression from the suggestion above and it worked, and I'm not sure what's the difference between two. Since we use * instead of version in exe path we need to test if this will work if there are multiple versions of NuGet.CommandLine package installed.

I didn't check if this works in IDE yet.

@awbacker
Copy link

awbacker commented Oct 29, 2017

(scrapping previous comment) @poma I actually integrated your changes from that PR into what I am using locally. What I ended up with was essentially yours, but with the property group to keep command lines shorter/clearer, and with dotted notation instead of the '%(FullPath)' way.

  • I had to use the ItemGroup method you showed (very nice!)
  • Adding a path with * in the property group didn't work, it could never resolve the exe. Maybe there is a way, but I don't know it.
  • Both forms of FullPath work for me. Not sure why I thought yours didn't. I'm on VS2017 so maybe the dotted form works for me but not vs2015 users.
  • Use single quotes instead of @quot; to avoid quoting issues with paths+spaces in Exec

Note: I don't think what you have above will work, since I can't (on vs2017) get the * method of exe locating to work on vs2017 with the property group, and still have to hard code the version number.

<!-- manually add the item group at the root level, along with the others -->
<ItemGroup>
  <!-- include nuget & squirrel with * paths.  possible issue if >1 version installed -->
  <NuGetCommandLine Include="..\packages\NuGet.CommandLine.*\tools\nuget.exe">
    <InProject>False</InProject>
  </NuGetCommandLine>
    <Squirrel Include="..\packages\Squirrel.Windows.*\tools\squirrel.exe">
    <InProject>False</InProject>
  </Squirrel>
</ItemGroup>

<Target Name="AfterBuild" Condition=" '$(Configuration)' == 'Release'">
  <GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
    <Output TaskParameter="Assemblies" ItemName="assemblyInfo" />
  </GetAssemblyIdentity>
  <PropertyGroup>
    <SemVerNumber>$([System.Version]::Parse(%(assemblyInfo.Version)).ToString(3))</SemVerNumber>
    <NuSpecFile>MyApp.nuspec</NuSpecFile>
    <ReleaseDir>..\Releases\</ReleaseDir>
    <!-- extra optional params for squirrel. can be empty -->
    <SquirrelParams>--no-msi</SquirrelParams>
  </PropertyGroup>
  <Error Condition="!Exists(%(NuGetCommandLine.FullPath))" Text="This project uses the `NuGet.CommandLine` package, but it is not installed." />
  <Error Condition="!Exists(%(Squirrel.FullPath))" Text="This project uses the `Squirrel.Windows package`, but it is not installed." />
  <!-- build into the local bin\Release\ directory temporarily -->
  <Exec Command='"%(NuGetCommandLine.FullPath)" pack $(NuSpecFile) -Version $(SemVerNumber) -Properties Configuration=Release -OutputDirectory $(OutDir) -BasePath $(OutDir)' />
  <!-- squirrelify into the release dir (usually at project level.  change the property above for a different location -->
  <Exec Command='"%(Squirrel.FullPath)" --releasify $(OutDir)MyApp.$(SemVerNumber).nupkg --releaseDir=$(ReleaseDir) $(SquirrelParams)' />
</Target>

@poma
Copy link
Author

poma commented Oct 29, 2017

A few more changes:

  • I've found in MSBuild docs that ItemGoup can be included in Target starting with .NET Framework 3.5
  • When included this way it doesn't need InProject tag
  • @(NuGetExe->'%(FullPath)') doesn't work with multiple packages installed but %(NuGetExe.FullPath) does. It gets the lowest version though
  • Visual Studio replaces single quotes with double anyway when you modify your project

Result:

  <Target Name="AfterBuild" Condition=" '$(Configuration)' == 'Release'">
    <GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
      <Output TaskParameter="Assemblies" ItemName="myAssemblyInfo" />
    </GetAssemblyIdentity>
    <ItemGroup>
      <!-- If your .NET version is <3.5 and you get build error, move this ItemGroup outside of Target -->
      <NuGetExe Include="..\packages\NuGet.CommandLine.*\tools\nuget.exe" />
      <SquirrelExe Include="..\packages\Squirrel.Windows.*\tools\squirrel.exe" />
    </ItemGroup>
    <PropertyGroup>
      <ReleaseDir>..\Releases\</ReleaseDir>
      <!-- Extra optional params for squirrel. can be empty -->
      <SquirrelParams>--no-msi</SquirrelParams>
      <SemVerNumber>$([System.Version]::Parse(%(myAssemblyInfo.Version)).ToString(3))</SemVerNumber>
    </PropertyGroup>
    <!-- Add some nice errors for the next person that comes along -->
    <Error Condition="!Exists(%(NuGetExe.FullPath))" Text="You are trying to use the NuGet.CommandLine package, but it is not installed. Please install NuGet.CommandLine from the Package Manager." />
    <Error Condition="!Exists(%(SquirrelExe.FullPath))" Text="You are trying to use the Squirrel.Windows package, but it is not installed. Please install Squirrel.Windows from the Package Manager." />
    <!-- Build nupkg into the project local bin\Release\ directory temporarily -->
    <Exec Command='"%(NuGetExe.FullPath)" pack $(TargetName).nuspec -Version $(SemVerNumber) -OutputDirectory $(OutDir) -BasePath $(OutDir)' />
    <!-- Squirrelify into the release dir (usually at solution level. Change the property above for a different location -->
    <Exec Command='"%(SquirrelExe.FullPath)" --releasify $(OutDir)MyApp.$(SemVerNumber).nupkg --releaseDir=$(ReleaseDir) $(SquirrelParams)' />
  </Target>

Looks like this result is ready to be included in docs. I'll update my PR

@JRLRJ
Copy link

JRLRJ commented Feb 23, 2018

@poma not sure where this is all at, but it would be nice to remove the one final reference to MyApp in the squirrel exec and replace with TargetName :)

@poma
Copy link
Author

poma commented May 25, 2018

It is fixed in the new PR #1120

@Thieum
Copy link
Contributor

Thieum commented May 6, 2019

@shiftkey the original issue was solved by a merged PR. The other points are in an ongoing one and could be tracked in #1120. This can be closed if we want to track this in the PR.

@shiftkey
Copy link
Contributor

shiftkey commented May 7, 2019

Closing this out in favour of the PR #1120

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests