Distribuisci un'applicazione UWP su un dispositivo Windows 10 dalla riga di comando con Cake

Distribuisci un'applicazione UWP su un dispositivo Windows 10 dalla riga di comando con Cake

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!