Selle teksti mõte on dokumenteerida enda tarbeks ClickOnce abil publitseeritava rakenduse komplekteerimine TFS build abil, kuna MSDN abitekstid (http://msdn.microsoft.com/en-us/library/xc3tc5xx.aspx) osutusid jällegi puudulikeks, vigasteks ja eksitavateks (jätsin sinna ka kommentaari).
Teiseks, eriti just SCSF rakenduse publitseerimisel ei saa kasutada out-of-the-box TFS Team Buildi, sest see nõuab projekti ülesehitust, kus kogu SCSF mõte kaob. SCSF tugevus seisneb aga selles, et rakendus on võimalik kokku panna erinevate arendajate poolt loodud moodulitest, mis ei pruugi olla ühes versioonikontrolli harus ega isegi mitte samas serveris. Või siis tarnivad mõned arendajad oma mooduli .dllina. Seega on vaja süsteemi, kus Team Build abil on võimalik kogu Smart Client rakendus kokku panna nö. algosadest.
ClickOnce rakenduse publitseerimisel võib olla vähemalt kaks lähtepunkti:
- rakendus on kompileeritud (dll-id, konfiguratsioonifailid jms.) ja see on vaja vaid komplekteerida (näiteks SmartClient) lisades/eemaldades komponente.
- rakendus on lähtekoodi kujul, vaja on kompileerida ja publitseerida.
Alljärgnev sobib mõlemale juhule, punkt 1. on punkti 2. alamosa.
1. Ettevalmistused
ClickOnce rakendus on kohustuslik signeerida. Selleks on vaja signeerimissertifikaati. Testrakenduse signeerimiseks sobib ka ise genereeritud sertifikaat. Windows SDK-s on selleks tööriist makecert
1. Loo sertifikaat: makecert -sv mykey.pvk -n “CN=myname” mykey.cer genereerib nii privaatvõtme kui ka sertifikaadi. CN=myname on muidugi vaid osa sertifikaadi tunnusest, reaalse sertifikaadi genereerimiseks on vaja rohkem infot.
2. Loo signeerimiseks vajalik võtmefail utiliidiga pvk2pfx: pvk2pfx -pvk mykey.pvk -spc mykey.cer -pfx mykey.pfx -po pfxparool, kus pfx parool on eraldi parool allkirjastamisel pfx võtme avamiseks ja ei tohiks olla sama mis privaatvõtme ja sertifikaadi genereerimisel.
3. Kontrolli, kas masinas (seega siis Team Build masin), kus töötad, on Publisher info olemas. Nimelt on ClickOnce publikatsiooni puhul vajalik Publisher info. Selle võid enne signeerimist kirjutada publikatsiooni manifesti teegina
<description asmv2:publisher=”<fimanimi>” asmv2:product=”<tootenimi>” asmv2:supportUrl=”<tugilehe URL>” xmlns=”urn:schemas-microsoft-com:asm.v1″ />
või siis märkida registrisse võtme
HKEY_LOCAL_MACHINE\SOFTWARE\MIcrosoft\Windows\
WIndowsNT\CurrentVersion\RegisteredOrganization alla.
2. Koodi ja teekide alla laadimine
Et me saaks koodi jms. laadida alla suvalistest versioonikontrolli harudest, peame ise tekitama workspace ja selle mappingud. Defineerime kogu keskkonna põhimuutujad – kuhu lähevad kompileeritud asjad, kuhu tõmmatakse lähtekoodid ja mis on loodava workspace nimi:
<OutputPath>C:\TFSBuilds\projekt\bin\</OutputPath>
<RootPath>C:\TFSBuilds\projekt\</RootPath>
<WorkspaceName>projektDEV</WorkspaceName>
<Tf>tf</Tf>
</PropertyGroup>
Edasi defineerime versioonikontrolli mappingud töökeskkonda:
<WorkspaceMapping Include=“$/DEV/kood“>
<LocalPath>$(RootPath)kood</LocalPath>
</WorkspaceMapping>
Muidugi võib neid mappinguid olla rohkem.
Seejärel tekitame töökeskkonna targetiga CreateWorkspace:
<!– Checking input parameters –>
<Error Condition=“$(WorkspaceName) == ”“ Text=“Please specify WorkspaceName property“/>
<Error Condition=“$(RootPath) == ”“ Text=“Please specify RootPath property“/>
<Error Condition=“!HasTrailingSlash(‘$(RootPath)’)“ Text=“Please make sure RootPath is slash terminated“/>
<Exec Command=“$(Tf) workspace /delete "$(WorkspaceName)"“ ContinueOnError=“true“ IgnoreExitCode=“true“/>
<!– Create new workspace–>
<Exec Command=“$(Tf) workspace /new /noprompt "$(WorkspaceName)"“ />
<!– Remove default mapping –>
<Exec Command=“$(Tf) workfold /unmap /workspace:"$(WorkspaceName)" $/“/>
<!– Create new mappings (uses MSBuild batching) –>
<Exec Command=“$(Tf) workfold /map "%(WorkspaceMapping.Identity)" "%(WorkspaceMapping.LocalPath)" /workspace:"$(WorkspaceName)"“/>
<!– Great success! –>
<Message Text=“Workspace ‘$(WorkspaceName)’ created sucessfully“/>
<!– List created mappings –>
<Exec Command=“$(Tf) workfold /workspace:"$(WorkspaceName)"“/>
</Target>
Laeme alla koodi loodud workspace:
<!–
Get the sources for the given workspace–><Get TeamFoundationServerUrl=“$(TeamFoundationServerUrl)“
BuildUri=“$(BuildUri)“
Workspace=“$(WorkspaceName)“
Version=“T“
Filespec=“$/DEV/kood“
PopulateOutput=“$(GetPopulateOutput)“
Overwrite=“$(GetOverwrite)“
Preview=“$(PreviewGet)“
Recursive=“$(RecursiveGet)“
Force=“$(ForceGet)“>
<Output TaskParameter=“Gets“ ItemName=“Gets“ />
<Output TaskParameter=“Replaces“ ItemName=“Replaces“ />
<Output TaskParameter=“Deletes“ ItemName=“Deletes“ />
<Output TaskParameter=“Warnings“ ItemName=“GetWarnings“ />
</Get>
Kus siis Filespec tähistab mingit elementi (haru, fail vms.) versioonikontrollist. Get lõiku kordame nii palju kui vaja, soovi korral võime laadida ka mitte viimase, vaid mingi muu versiooni jne.
Edasi puhastame kompileerimiskoha:
<RemoveDir Directories=“$(OutputPath)“/>
<MakeDir Directories=“$(OutputPath)“/>
</Target>
Seejärel kompileerime projektid, mille tõmbasime alla lähtekoodina:
<MSBuild Projects=“$(RootPath)kood\kood.csproj“
Properties=“OutputPath=$(OutputPath);NoWin32Manifest=true;“/>
</Target>
VS2008 ja ClickOnce puhul on eriti oluline kompileerimislipp NoWin32Manifest=true, et kompileerimisel ei lisataks manifesti kompilatsioonidesse. Pärast rakenduse manifesti genereerimisel satub see vastuollu ClickOnce manifestiga ja tulemuseks on müstiline (MS poolt lahti seletamata) veateade “Reference in the manifest does not match the identity of the downloaded assembly”.
Edasi defineerime ClickOnce rakenduse versiooni ja publitseerimise muutujad, kus ShellExe on SCSF rakenduse koorik, DeploymentDir on koht, kuhu publitseeritakse rakendus (NB! versiooni alamkataloog luuakse hiljem), DeployUrl on aga koht, kust rakendus kasutajale paistma hakkab (jälle ilma versiooniharuta):
<Major>1</Major>
<Minor>0</Minor>
<Build>0</Build>
<Revision>0</Revision>
</PropertyGroup>
<ShellExe>Shell.exe</ShellExe>
<DeploymentDir>C:\software\dev\</DeploymentDir>
<DeployUrl>http://software.dev.ee/dev/</DeployUrl>
<SupportUrl>http://www.dev.ee/</SupportUrl>
</PropertyGroup>
Edasi defineerime rakenduse koosluse koorikust (exe), teekidest (dll) ja failidest (nii tavalised kui andmefailid):
<EntryPoint Include=“$(OutputPath)$(ShellExe)“/>
<
Dependency Include=“$(OutputPath)Microsoft.Practices.CompositeUI.dll“><AssemblyType>Managed</AssemblyType>
<DependencyType>Install</DependencyType>
</Dependency>
<TargetPath>ProfileCatalog.xml</TargetPath>
</File>
<
File Include=“$(OutputPath)Data\Data.sdf“><TargetPath>Data\Data.sdf</TargetPath>
<IsDataFile>true</IsDataFile>
</File>
Nüüd saame publitseerida rakenduse alamversiooniga kataloogi:
<Version VersionFile=“$(RootPath)versionnumber.txt“ BuildType=“None“ RevisionType=“Increment“>
<Output TaskParameter=“Major“ PropertyName=“Major“ />
<Output TaskParameter=“Minor“ PropertyName=“Minor“ />
<Output TaskParameter=“Build“ PropertyName=“Build“ />
<Output TaskParameter=“Revision“ PropertyName=“Revision“ />
</Version>
<GenerateApplicationManifest
AssemblyVersion=“$(Major).$(Minor).$(Build).$(Revision)“
Dependencies=“@(Dependency)“
Description=“Shell“
Files=“@(File)“
EntryPoint=“$(OutputPath)$(ShellExe)“
OutputManifest=“$(OutputPath)$(ShellExe).manifest“>
<Output
ItemName=“ApplicationManifest“
TaskParameter=“OutputManifest“
/>
</GenerateApplicationManifest>
<Exec Command=“mage -s $(OutputPath)$(ShellExe).manifest -cf $(RootPath)mykey.pfx -pwd pfxparool“/>
<GenerateDeploymentManifest
EntryPoint=“@(ApplicationManifest)“
AssemblyName=“$(ShellExe).application“
AssemblyVersion=“$(Major).$(Minor).$(Build).$(Revision)“
DeploymentUrl=“$(DeployUrl)$(Major)_$(Minor)_$(Build)_$(Revision)/$(ShellExe).application“
MapFileExtensions=“true“
Install=“false“
OutputManifest=“$(OutputPath)$(ShellExe).application“
Product=“Albert Editor“
Publisher=“AS Firma“
SupportUrl=http://www.firma.ee/
UpdateEnabled=“false“>
<Output
ItemName=“DeployManifest“
TaskParameter=“OutputManifest“
/>
</GenerateDeploymentManifest>
<Exec Command=“mage -s $(OutputPath)$(ShellExe).application -cf $(RootPath)mykey.pfx -pwd pfxparool“/>
<CreateItem
Include=“$(OutputPath)**\*.*“
Exclude=“$(OutputPath)**\*.manifest;$(OutputPath)**\*.application“
>
<Output ItemName=“DeploymentFiles“ TaskParameter=“Include“/>
</CreateItem>
<Copy
SourceFiles=“@(DeploymentFiles)“
DestinationFiles=“@(DeploymentFiles->’$(DeploymentDir)$(Major)_$(Minor)_$(Build)_$(Revision)\%(RecursiveDir)%(Filename)%(Extension).deploy’)“/>
<Copy SourceFiles=“$(OutputPath)$(ShellExe).manifest“ DestinationFolder=“$(DeploymentDir)$(Major)_$(Minor)_$(Build)_$(Revision)“/>
<Copy SourceFiles=“$(OutputPath)$(ShellExe).application“ DestinationFolder=“$(DeploymentDir)$(Major)_$(Minor)_$(Build)_$(Revision)“/>
</Target>