Poprvé jsem byl postaven před nutnost napsat instalační skript pro NuGet balíček. Chtěl jsem úplně triviální věc – přidat do projektu link na několik souborů a nastavit jim vlastnost "Copy to Output Directory" na "Copy if newer". Nešlo to úplně tak hladce a proto si zde o tom ublogávám.
NuGet je boží. Předpokládám, že laskavý čtenář, který zabloudil na tyto stránky, již tuší o co jde. V opačném případě je možné, že vás sem nasměroval zlomyslný vyhledávač, a to co hledáte je tady.
Přidat do NuGet balíčku instalační skript je úplně jednoduché. Stačí vytvořit soubor s názvem install.ps1 a nakopírovat ho do složky tools (v případě že vytváříte balíček z convention-based directory), nebo ho přidat v .nuspec souboru pod target tools, například takto:
<files> <file src="*.ps1" target="tools"/> </files>
Tím ovšem začíná dobrodružnější část: Zjistit, co má takový install.ps1 vlastně obsahovat a zajistit, aby fungoval.
Nejdříve musíme definovat hlavičku:
param($installPath, $toolsPath, $package, $project)
Parametr $toolsPath obsahuje cestu ke složce tools. Do té jsme si v balíčku připravili soubory, které budeme přidávat do projektu. Dále použijeme parametr $project typu EnvDTE.Project, který nám umožní manipulovat s Visual Studiovým projektem a jeho soubory. Hlavním smyslem typu EnvDTE.Project je zúžit okruh lidí, kteří budou psát rozšíření pro Visual Studio, takže zjistit jak přidat soubor a nastavit mu vlastnosti není úplně jednoduché.
Pro prozkoumávání struktury uložené v parametru $project se mi osvědčila Package Manager Console přímo ve Visual Studiu. Je to normální PowerShellová konzole, kterou vám do VS přidá při své instalaci NuGet. Příkazem Get-Project se dostaneme na objekt reprezentující projekt a jeho vlastnosti. Například seznam souborů v projektu dostaneme takto:
PM> get-project -name Dkl.Demo | foreach { $_.ProjectItems } | select { $_.Name }
Přidat nový soubor (jako link) je jednoduché, stačí použít metodu ProjectItems.AddFromFile. Horší je pak nastavit nějakou jeho vlastnost, protože jsou definované dynamicky a liší se podle typu projektu. Pro začátek si vypíšeme všechny vlastnosti určitého souboru:
PM> get-project -name Dkl.Demo | foreach { $_.ProjectItems.Item('Class.cs').Properties } | select { $_.Name, $_.Value } $_.Name, $_.Value ------------------- {Extension, .cs} {FileName, Class.cs} ... {BuildAction, 1} ... {CopyToOutputDirectory, 0} ...
Takže už víme, že vlastnost kterou chceme nastavit, se jmenuje CopyToOutputDirectory, hurá! Teď ještě zjistit, co znamená hodnota 0, a jaké číslo použít když chceme nastavit "Copy if newer". Když už máme přesný název property, proklepnul jsem gůgl, jestli nenajde nějakou specifikaci na hlubokém internetu MSDN, a ejhle, objevil jsem dobře ukrytou perlu jmenných konvencí, VSLangProj80.FileProperties2. Bohužel ale ani zde není popsáno, jakou hodnotu použít pro CopyToOutputDirectory.
Nakonec jsem se uchýlil k podlému triku – vytvořil jsem v projektu soubor, kterému jsem nastavil CopyToOutputDirectory na "Copy if newer" a zjistil hodnotu tímto příkazem (všimněte si, že jsem musel použít foreach - čekal bych select, ale ten vypíše nic neříkající "System.__ComObject"):
PM> get-project -name Gmc.Fido.Shared | foreach { $_.ProjectItems.Item('Common.Logging.dll').Properties.Item('CopyToOutputDirectory') } Value : 2 NumIndices : 0 Application : Parent : Name : CopyToOutputDirectory Collection : System.__ComObject Object : DTE : System.__ComObjectUff, takže 2.
Konečně se můžeme vrátit k instalačnímu skriptu, který přidá soubory ze složky tools v balíčku jako linky do projektu a nastaví všem "Copy if newer":
param($installPath, $toolsPath, $package, $project) Get-ChildItem -path $toolsPath | Where-Object { $_.Name -match '.*\.dll' } | ForEach-Object { $i = $project.ProjectItems.AddFromFile($_.FullName); $i.Properties.Item("CopyToOutputDirectory").Value = 2 }
Zbývá ještě napsat odinstalační skript, který NuGet spustí při odebrání balíčku nebo jeho upgrade na vyšší verzi. Přidáme do tools soubor uninstall.ps1, který bude vypadat takto:
param($installPath, $toolsPath, $package, $project) $project.ProjectItems | Where-Object { $_.Name -match '.*\.dll' } | ForEach-Object { $_.Remove() }
A je to! Zbývá už jen pár silných tvrzení na závěr: NuGet je boží. EnvDTE není boží. EnvDTE dost sukuje.
Žádné komentáře:
Okomentovat