Da un po' di tempo volevo migliorare il mio processo di integrazione continua per la creazione, il test e la distribuzione di applicazioni UWP. Per queste app UWP, sono stato legato all'utilizzo di VS2017 per operazioni di compilazione e distribuzione e VS2017 è fantastico, ma mi sono sentito limitato dalla natura "punta e clicca" di queste operazioni in VS2017.
L'esecuzione di test automatici per qualsiasi progetto .NET è ben documentata, ma fino a tempi relativamente recenti non avevo un modo davvero buono per utilizzare una riga di comando per:
- crea il mio progetto e la mia soluzione UWP,
- esegui test per la soluzione,
- crea un file .appxbundle se i test hanno esito positivo e
- e distribuisci appxbundle sul mio dispositivo Windows 10.
Cercare di scoprire cosa sta succedendo sotto il cofano è il tipo di sfida che è un problema per me, e questa è la mia occasione per condividere ciò che ho imparato con la comunità.
Fase 1:crea l'UWP demo e testa i progetti.
Manterrò la descrizione di questo un po' veloce:userò semplicemente il modello UWP in Visual Studio 2017:è solo uno schermo bianco vuoto ma va bene per questa dimostrazione.
Ho anche creato un progetto di unit test vuoto:ancora una volta la funzione non è importante per questa dimostrazione, abbiamo solo bisogno di un progetto con uno unit test eseguibile.
Ho scritto un semplice "test" fittizio, mostrato di seguito:questo è appena creato allo scopo di dimostrare come Cake può eseguire un progetto Unit Test scritto utilizzando MSTest:
using Microsoft.VisualStudio.TestTools.UnitTesting; namespace UnitTestProject2 { [TestClass] public class UnitTest1 { [TestMethod] public void TestMethod1() { Assert.IsTrue(true); } } }
Fase 2:costruiamo il nostro progetto ed eseguiamo i test utilizzando Cake
Aprire un prompt di PowerShell (io uso la console di gestione dei pacchetti in VS2017) e passare alla cartella del progetto UWP. Ora ottieni lo script del bootstrapper di Cake e un file di build Cake di esempio usando i comandi seguenti:
Invoke-WebRequest http://cakebuild.net/download/bootstrapper/windows -OutFile build.ps1 Invoke-WebRequest https://raw.githubusercontent.com/cake-build/example/master/build.cake -OutFile build.cake
Ho modificato il file build.cake per avere il testo seguente: questo script pulisce i binari, ripristina i pacchetti NuGet per i progetti, li compila ed esegue i MSTest che abbiamo creato.
#tool nuget:?package=NUnit.ConsoleRunner&version=3.4.0 ////////////////////////////////////////////////////////////////////// // ARGUMENTS ////////////////////////////////////////////////////////////////////// var target = Argument("target", "Default"); var configuration = Argument("configuration", "Release"); ////////////////////////////////////////////////////////////////////// // PREPARATION ////////////////////////////////////////////////////////////////////// // Define directories. var buildDir = Directory("./App3/bin") + Directory(configuration); ////////////////////////////////////////////////////////////////////// // TASKS ////////////////////////////////////////////////////////////////////// Task("Clean") .Does(() => { CleanDirectory(buildDir); }); Task("Restore-NuGet-Packages") .IsDependentOn("Clean") .Does(() => { NuGetRestore("../App3.sln"); }); Task("Build") .IsDependentOn("Restore-NuGet-Packages") .Does(() => { if(IsRunningOnWindows()) { // Use MSBuild MSBuild("../App3.sln", settings => settings.SetConfiguration(configuration)); } else { // Use XBuild XBuild("../App3.sln", settings => settings.SetConfiguration(configuration)); } }); Task("Run-Unit-Tests") .IsDependentOn("Build") .Does(() => { MSTest("../**/bin/" + configuration + "/UnitTestProject2.dll"); }); ////////////////////////////////////////////////////////////////////// // TASK TARGETS ////////////////////////////////////////////////////////////////////// Task("Default") .IsDependentOn("Run-Unit-Tests"); ////////////////////////////////////////////////////////////////////// // EXECUTION ////////////////////////////////////////////////////////////////////// RunTarget(target);
Il benchmarking integrato di Cake mostra l'ordine in cui vengono eseguite le attività
Task Duration -------------------------------------------------- Clean 00:00:00.0124995 Restore-NuGet-Packages 00:00:03.5300892 Build 00:00:00.8472346 Run-Unit-Tests 00:00:01.4200992 Default 00:00:00.0016743 -------------------------------------------------- Total: 00:00:05.8115968
E ovviamente se qualcuno di questi passaggi avesse fallito (ad esempio se un test fallito), l'esecuzione si sarebbe interrotta a questo punto.
Fase 3:creazione di un AppxBundle in Cake
Se voglio creare un appxbundle per un progetto UWP dalla riga di comando, eseguirei il codice seguente:
MSBuild ..\App3\App3.csproj /p:AppxBundle=Always /p:AppxBundlePlatforms="x86|arm" /Verbosity:minimal
Ci sono quattro argomenti di cui MSBuild ha parlato:
- Il percorso del file csproj a cui voglio indirizzare
- Voglio creare l'AppxBundle
- Voglio puntare su piattaforme x86 e ARM (ARM non funziona da solo)
- E voglio ridurre al minimo la verbosità dei log di output.
Potrei usare StartProcess per fare in modo che Cake esegua MSBuild in un'attività, ma Cake ha già metodi per MSBuild (e molti dei suoi parametri) integrati. Per quei parametri che Cake non conosce, è molto facile usare WithProperty fluent metodo per aggiungere il parametro e il valore dell'argomento. Il codice seguente mostra come implementare il comando per creare AppxBundle nella sintassi C# di Cake.
var applicationProjectFile = @"../App3/App3.csproj"; // ... MSBuild(applicationProjectFile, new MSBuildSettings { Verbosity = Verbosity.Minimal } .WithProperty("AppxBundle", "Always") .WithProperty("AppxBundlePlatforms", "x86|arm") );
Dopo l'esecuzione di questo codice in un'attività, viene generato un AppxBundle in una cartella del progetto con il percorso:
AppPackages\App3_1.0.0.0_Debug_Test\App3_1.0.0.0_x86_arm_Debug.appxbundle
Il percorso e il nome del file non sono leggibili in modo massiccio ed è anche probabile che cambino, quindi ho scritto un breve metodo per cercare le directory del progetto e restituire il percorso del primo AppxBundle trovato.
private string FindFirstAppxBundlePath() { var files = System.IO.Directory.GetFiles(@"..\", @"*.appxbundle", SearchOption.AllDirectories); if (files.Count() > 0) { return files[0]; } else { throw new System.Exception("No appxbundle found"); } }
Ora che ho il percorso per AppxBundle, sono pronto per distribuirlo sul mio dispositivo Windows.
Fase 4:distribuzione dell'AppxBundle
Microsoft ha fornito uno strumento da riga di comando nell'SDK di Windows 10 per la distribuzione di AppxBundles:questo strumento è chiamato WinAppDeployCmd. La sintassi utilizzata per distribuire un AppxBundle è:
WinAppDeployCmd install -file "\MyApp.appxbundle" -ip 192.168.0.1
È molto semplice utilizzare uno strumento da riga di comando con Cake:ne ho già parlato sul blog e su come utilizzare StartProcess per chiamare un eseguibile di cui il contesto di Cake è a conoscenza.
Ma che dire degli strumenti da riga di comando di cui Cake non è a conoscenza? Si scopre che è facile registrare gli strumenti nel contesto di Cake:devi solo conoscere il percorso dello strumento e il codice seguente mostra come aggiungere lo strumento di distribuzione dell'app UWP al contesto:
Setup(context => { context.Tools.RegisterFile(@"C:\Program Files (x86)\Windows Kits\10\bin\x86\WinAppDeployCmd.exe"); });
Quindi, con questo strumento nel contesto di Cake, è molto semplice creare un'attività dedicata ed estrarre i dettagli di questo strumento fuori contesto per l'uso con StartProcess, come mostrato di seguito.
Task("Deploy-Appxbundle") .IsDependentOn("Build-Appxbundle") .Does(() => { FilePath deployTool = Context.Tools.Resolve("WinAppDeployCmd.exe"); Information(appxBundlePath); var processSuccessCode = StartProcess(deployTool, new ProcessSettings { Arguments = new ProcessArgumentBuilder() .Append(@"install") .Append(@"-file") .Append(appxBundlePath) .Append(@"-ip") .Append(raspberryPiIpAddress) }); if (processSuccessCode != 0) { throw new Exception("Deploy-Appxbundle: UWP application was not successfully deployed"); } });
E ora possiamo eseguire il nostro script Cake per creare e distribuire automaticamente l'applicazione UWP:ho incollato le statistiche di benchmarking di Cake di seguito.
Task Duration -------------------------------------------------- Clean 00:00:00.0821960 Restore-NuGet-Packages 00:00:09.7173174 Build 00:00:01.5771689 Run-Unit-Tests 00:00:03.2204312 Build-Appxbundle 00:01:09.6506712 Deploy-Appxbundle 00:02:13.8439852 -------------------------------------------------- Total: 00:03:38.0917699
E per dimostrare che è stato effettivamente distribuito, ecco uno screenshot dell'elenco delle app sul mio Raspberry Pi (dal portale del dispositivo) prima di eseguire lo script...
... ed eccone uno dopo:puoi vedere che l'app UWP è stata distribuita correttamente.
Ho caricato il file build.cake del mio progetto in un documento pubblico:puoi copiarlo e cambiarlo per adattarlo al tuo progetto particolare (non ho caricato un progetto UWP completo perché a volte le persone hanno problemi con il file *.pfx ).
Conclusione
Ho scoperto che è possibile creare e distribuire un'app UWP utilizzando la riga di comando e, oltre a ciò, è possibile integrare il processo di compilazione e distribuzione in uno script Cake. Quindi, anche se creo ancora la mia applicazione in VS2017, e probabilmente continuerò a utilizzare VS2017, significa che ho un processo di integrazione molto più strutturato e automatizzato.
Su di me: Posto regolarmente su .NET:se sei interessato, seguimi su Twitter o dai un'occhiata ai miei post precedenti qui. Grazie!