341 строка
14 KiB
C#
341 строка
14 KiB
C#
#tool nuget:?package=XamarinComponent
|
|
#addin nuget:?package=Cake.FileHelpers&version=2.0.0
|
|
#addin nuget:?package=Cake.Xamarin
|
|
#addin nuget:?package=Newtonsoft.Json
|
|
#load "utility.cake"
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Net;
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
// Task Target for build
|
|
var Target = Argument("target", Argument("t", "Default"));
|
|
|
|
string ArchiveDirectory = "archives";
|
|
bool IsMandatory = false;
|
|
string DistributionGroup = "Private Release Script Group";
|
|
string Token = EnvironmentVariable("APP_CENTER_API_TOKEN");
|
|
string BaseUrl = "https://api.appcenter.ms";
|
|
ApplicationInfo CurrentApp = null;
|
|
string ProjectPath = "AppCenterDemoApp";
|
|
string BuildFolder = GetBuildFolder("Demo", ProjectPath);
|
|
|
|
public enum Environment
|
|
{
|
|
Int,
|
|
Prod
|
|
}
|
|
|
|
public enum Platform
|
|
{
|
|
iOS,
|
|
Android,
|
|
UWP
|
|
}
|
|
|
|
public enum ScriptingBackend
|
|
{
|
|
Il2Cpp,
|
|
Mono,
|
|
DotNet
|
|
}
|
|
|
|
public class ApplicationInfo
|
|
{
|
|
public static string OutputDirectory;
|
|
public Environment AppEnvironment { get; }
|
|
public ScriptingBackend AppScriptingBackend { get; }
|
|
public Platform AppPlatform { get; }
|
|
public string AppOwner { get; }
|
|
public string AppId { get; }
|
|
public string BuildMethod { get; }
|
|
public string IncrementVersionMethod { get; }
|
|
|
|
public string AppPath
|
|
{
|
|
get
|
|
{
|
|
return OutputDirectory + "/" + AppId + "." + _appExtension;
|
|
}
|
|
}
|
|
|
|
private string _appExtension = null;
|
|
public ApplicationInfo(Environment environment, Platform platform, ScriptingBackend backend, string appOwner, string appId, string buildMethod = "", string incrementVersionMethod = "", string appExtension = "")
|
|
{
|
|
AppOwner = appOwner;
|
|
AppId = appId;
|
|
AppScriptingBackend = backend;
|
|
AppEnvironment = environment;
|
|
AppPlatform = platform;
|
|
BuildMethod = buildMethod;
|
|
IncrementVersionMethod = incrementVersionMethod;
|
|
_appExtension = appExtension;
|
|
}
|
|
}
|
|
|
|
ApplicationInfo.OutputDirectory = ArchiveDirectory;
|
|
IList<ApplicationInfo> Applications = new List<ApplicationInfo>
|
|
{
|
|
new ApplicationInfo(Environment.Int, Platform.iOS, ScriptingBackend.Mono, "alchocro", "iOS-Unity-Puppet", "BuildPuppet.BuildPuppetSceneIosMonoDeviceSdk", "BuildPuppet.IncrementVersionNumber", "ipa"),
|
|
new ApplicationInfo(Environment.Int, Platform.iOS, ScriptingBackend.Il2Cpp, "alchocro", "iOS-Unity-Puppet", "BuildPuppet.BuildPuppetSceneIosIl2CPPDeviceSdk", "BuildPuppet.IncrementVersionNumber", "ipa"),
|
|
new ApplicationInfo(Environment.Int, Platform.Android, ScriptingBackend.Mono, "alchocro", "Unity-Android-Puppet", "BuildPuppet.BuildPuppetSceneAndroidMono", "BuildPuppet.IncrementVersionNumber", "apk"),
|
|
new ApplicationInfo(Environment.Int, Platform.Android, ScriptingBackend.Il2Cpp, "alchocro", "Unity-Android-Puppet", "BuildPuppet.BuildPuppetSceneAndroidIl2CPP", "BuildPuppet.IncrementVersionNumber", "apk"),
|
|
new ApplicationInfo(Environment.Int, Platform.UWP, ScriptingBackend.DotNet, "alchocro", "UWP-Unity-Puppet"),
|
|
new ApplicationInfo(Environment.Int, Platform.UWP, ScriptingBackend.Il2Cpp, "alchocro", "UWP-Unity-Puppet"),
|
|
new ApplicationInfo(Environment.Prod, Platform.iOS, ScriptingBackend.Mono, "mobile-center-sdk", "iOS-Unity-Demo-App", "BuildPuppet.BuildPuppetSceneIosMonoDeviceSdk", "BuildPuppet.IncrementVersionNumber", "ipa"),
|
|
new ApplicationInfo(Environment.Prod, Platform.iOS, ScriptingBackend.Il2Cpp, "mobile-center-sdk", "iOS-Unity-Demo-App", "BuildPuppet.BuildPuppetSceneIosIl2CPPDeviceSdk", "BuildPuppet.IncrementVersionNumber", "ipa"),
|
|
new ApplicationInfo(Environment.Prod, Platform.Android, ScriptingBackend.Mono, "mobile-center-sdk", "Android-Unity-Demo-App", "BuildPuppet.BuildPuppetSceneAndroidMono", "BuildPuppet.IncrementVersionNumber", "apk"),
|
|
new ApplicationInfo(Environment.Prod, Platform.Android, ScriptingBackend.Il2Cpp, "mobile-center-sdk", "Android-Unity-Demo-App", "BuildPuppet.BuildPuppetSceneAndroidIl2CPP", "BuildPuppet.IncrementVersionNumber", "apk"),
|
|
new ApplicationInfo(Environment.Prod, Platform.UWP, ScriptingBackend.DotNet, "mobile-center-sdk", "UWP-Unity-Demo-App"),
|
|
new ApplicationInfo(Environment.Prod, Platform.UWP, ScriptingBackend.Il2Cpp, "mobile-center-sdk", "UWP-Unity-Demo-App")
|
|
};
|
|
|
|
Setup(context =>
|
|
{
|
|
// Arguments:
|
|
// -Environment: App "environment" ("prod" or "int") -- Default is "int"
|
|
// -Group: Distribution group name -- Default is "Private Release Script Group"
|
|
// -Mandatory: Should the release be mandatory ("true" or "false") -- Default is "false"
|
|
// -Platform: ios, android, or uwp -- Default is "ios"
|
|
// -ScriptingBackend: dotnet, mono, or il2cpp -- Default is "il2cpp"
|
|
|
|
// Read arguments
|
|
var environment = Environment.Prod;
|
|
if (Argument("Environment", "int") == "int")
|
|
{
|
|
environment = Environment.Int;
|
|
Token = EnvironmentVariable("APP_CENTER_INT_API_TOKEN");
|
|
BaseUrl = "https://asgard-int.trafficmanager.net/api";
|
|
ProjectPath = ".";
|
|
BuildFolder = GetBuildFolder("Puppet", ProjectPath);
|
|
}
|
|
|
|
var platformString = Argument<string>("Platform", "ios");
|
|
var platform = Platform.iOS;
|
|
|
|
if (platformString == "android")
|
|
{
|
|
platform = Platform.Android;
|
|
}
|
|
else if (platformString == "uwp")
|
|
{
|
|
platform = Platform.UWP;
|
|
}
|
|
|
|
var backend = ScriptingBackend.Il2Cpp;
|
|
var backendString = Argument("ScriptingBackend", "il2cpp");
|
|
if (backendString == "dotnet")
|
|
{
|
|
backend = ScriptingBackend.DotNet;
|
|
}
|
|
else if (backendString == "mono")
|
|
{
|
|
backend = ScriptingBackend.Mono;
|
|
}
|
|
|
|
try
|
|
{
|
|
CurrentApp = ( from app in Applications
|
|
where app.AppPlatform == platform &&
|
|
app.AppEnvironment == environment &&
|
|
app.AppScriptingBackend == backend
|
|
select app
|
|
).Single();
|
|
}
|
|
catch
|
|
{
|
|
Error("No application configuration exists with the given arguments.");
|
|
}
|
|
DistributionGroup = Argument<string>("Group", DistributionGroup);
|
|
DistributionGroup = DistributionGroup.Replace('_', ' ');
|
|
IsMandatory = Argument<bool>("Mandatory", false);
|
|
});
|
|
|
|
// Distribution Tasks
|
|
|
|
Task("CreateIosArchive").IsDependentOn("IncreaseIosVersion")
|
|
.Does(()=>
|
|
{
|
|
ExecuteUnityMethod(CurrentApp.BuildMethod, "ios", ProjectPath);
|
|
var xcodeProjectPath = GetDirectories(BuildFolder + "/*/*.xcodeproj").Single().FullPath;
|
|
Information("Creating archive...");
|
|
var archiveName = Statics.TemporaryPrefix + "iosArchive.xcarchive";
|
|
var result = StartProcess("xcodebuild", "-project \"" + xcodeProjectPath + "\" -configuration Release -scheme Unity-iPhone -archivePath \"" + archiveName + "\" archive");
|
|
if (result != 0)
|
|
{
|
|
throw new Exception("Failed to archive ipa. Result code from xcodebuild: " + result);
|
|
}
|
|
|
|
// Just create the empty plist file here so it doesn't cluttering the repo.
|
|
var plistName = Statics.TemporaryPrefix + "exportoptions.plist";
|
|
System.IO.File.WriteAllText(plistName,
|
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
|
|
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">" +
|
|
"<plist version=\"1.0\"><dict></dict></plist>");
|
|
Information("Creating ipa...");
|
|
DeleteFileIfExists(CurrentApp.AppPath);
|
|
result = StartProcess("xcodebuild", "-exportArchive -archivePath \"" + archiveName +
|
|
"\" -exportPath \"" + CurrentApp.AppPath +
|
|
"\" -exportOptionsPlist \"" + plistName + "\"");
|
|
if (result != 0)
|
|
{
|
|
throw new Exception("Failed to export ipa. Result code from xcodebuild: " + result);
|
|
}
|
|
var ipaFile = GetFiles(CurrentApp.AppPath + "/*.ipa").Single();
|
|
var temporaryIpaPath = ArchiveDirectory + "/" + Statics.TemporaryPrefix + "tempIpa.ipa";
|
|
MoveFile(ipaFile, temporaryIpaPath);
|
|
DeleteDirectoryIfExists(CurrentApp.AppPath);
|
|
var temporaryIpaFile = File(temporaryIpaPath);
|
|
MoveFile(temporaryIpaFile, CurrentApp.AppPath);
|
|
}).Finally(() => RunTarget("RemoveTemporaries"));
|
|
|
|
Task("CreateAndroidArchive").IsDependentOn("IncreaseAndroidVersion").Does(()=>
|
|
{
|
|
ExecuteUnityMethod(CurrentApp.BuildMethod, "android", ProjectPath);
|
|
var apkFile = GetFiles(BuildFolder + "/*.apk").Single();
|
|
MoveFile(apkFile, CurrentApp.AppPath);
|
|
}).Finally(() => RunTarget("RemoveTemporaries"));
|
|
|
|
Task("IncreaseIosVersion").Does(()=>
|
|
{
|
|
ExecuteUnityMethod(CurrentApp.IncrementVersionMethod, "ios", ProjectPath);
|
|
});
|
|
|
|
Task("IncreaseAndroidVersion").Does(()=>
|
|
{
|
|
ExecuteUnityMethod(CurrentApp.IncrementVersionMethod, "android", ProjectPath);
|
|
});
|
|
|
|
Task("ReleaseApplication")
|
|
.Does(()=>
|
|
{
|
|
if (CurrentApp.AppPlatform == Platform.iOS)
|
|
{
|
|
RunTarget("CreateIosArchive");
|
|
}
|
|
else if (CurrentApp.AppPlatform == Platform.Android)
|
|
{
|
|
RunTarget("CreateAndroidArchive");
|
|
}
|
|
else
|
|
{
|
|
Error("Cannot distribute for this platform.");
|
|
return;
|
|
}
|
|
|
|
// Start the upload.
|
|
Information("Initiating distribution process...");
|
|
var startUploadUrl = GetApiUrl(BaseUrl, CurrentApp.AppOwner, CurrentApp.AppId, "release_uploads");
|
|
var startUploadRequest = GetWebRequest(startUploadUrl, Token);
|
|
var startUploadResponse = GetResponseJson(startUploadRequest);
|
|
|
|
// Upload the file to the given endpoint. The label "ipa" is correct for all platforms.
|
|
var uploadUrl = startUploadResponse["upload_url"].ToString();
|
|
HttpUploadFile(uploadUrl, CurrentApp.AppPath, "ipa");
|
|
|
|
// Commit the upload
|
|
Information("Committing distribution...");
|
|
var uploadId = startUploadResponse["upload_id"].ToString();
|
|
var commitRequestUrl = startUploadUrl + "/" + uploadId;
|
|
var commitRequest = GetWebRequest(commitRequestUrl, Token, "PATCH");
|
|
AttachJsonPayload(commitRequest,
|
|
new JObject(
|
|
new JProperty("status", "committed")));
|
|
var commitResponse = GetResponseJson(commitRequest);
|
|
var releaseUrl = BaseUrl + "/" + commitResponse["release_url"].ToString();
|
|
|
|
// Release the upload
|
|
Information("Finalizing release...");
|
|
var releaseRequest = GetWebRequest(releaseUrl, Token, "PATCH");
|
|
var releaseNotes = "This release has been created by the script test-tools.cake.";
|
|
AttachJsonPayload(releaseRequest,
|
|
new JObject(
|
|
new JProperty("destination_name", DistributionGroup),
|
|
new JProperty("release_notes", releaseNotes),
|
|
new JProperty("mandatory", IsMandatory.ToString().ToLower())));
|
|
releaseRequest.GetResponse().Dispose();
|
|
var mandatorySuffix = IsMandatory ? " as a mandatory update" : "";
|
|
Information("Successfully released " + CurrentApp.AppOwner +
|
|
"/" + CurrentApp.AppId + " to group " +
|
|
DistributionGroup + mandatorySuffix + ".");
|
|
});
|
|
|
|
// Helper methods
|
|
|
|
string GetApiUrl(string baseUrl, string appOwner, string appId, string apiName)
|
|
{
|
|
return string.Format("{0}/v0.1/apps/{1}/{2}/{3}", baseUrl, appOwner, appId, apiName);
|
|
}
|
|
|
|
JObject GetResponseJson(HttpWebRequest request)
|
|
{
|
|
using (var response = request.GetResponse())
|
|
using (var reader = new StreamReader(response.GetResponseStream()))
|
|
{
|
|
return JObject.Parse(reader.ReadToEnd());
|
|
}
|
|
}
|
|
|
|
HttpWebRequest GetWebRequest(string url, string token, string method = "POST")
|
|
{
|
|
Information(string.Format("About to call url '{0}'", url));
|
|
var request = (HttpWebRequest)WebRequest.Create(url);
|
|
request.Headers["X-API-Token"] = token;
|
|
request.ContentType = "application/json";
|
|
request.Accept = "application/json";
|
|
request.Method = method;
|
|
return request;
|
|
}
|
|
|
|
void AttachJsonPayload(HttpWebRequest request, JObject json)
|
|
{
|
|
using (var stream = request.GetRequestStream())
|
|
using (var sr = new StreamWriter(stream))
|
|
{
|
|
sr.Write(json.ToString());
|
|
}
|
|
}
|
|
|
|
// Adapted from https://stackoverflow.com/questions/566462/upload-files-with-httpwebrequest-multipart-form-data/2996904#2996904
|
|
void HttpUploadFile(string url, string file, string paramName)
|
|
{
|
|
Information(string.Format("Uploading {0} to {1}", file, url));
|
|
var boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
|
|
byte[] boundaryBytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");
|
|
var request = (HttpWebRequest)WebRequest.Create(url);
|
|
request.ContentType = "multipart/form-data; boundary=" + boundary;
|
|
request.Method = "POST";
|
|
request.KeepAlive = true;
|
|
using (var requestStream = request.GetRequestStream())
|
|
{
|
|
requestStream.Write(boundaryBytes, 0, boundaryBytes.Length);
|
|
var headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\n\r\n";
|
|
var header = string.Format(headerTemplate, paramName, file);
|
|
byte[] headerBytes = System.Text.Encoding.UTF8.GetBytes(header);
|
|
requestStream.Write(headerBytes, 0, headerBytes.Length);
|
|
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read))
|
|
{
|
|
byte[] buffer = new byte[4096];
|
|
var bytesRead = 0;
|
|
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
|
|
{
|
|
requestStream.Write(buffer, 0, bytesRead);
|
|
}
|
|
}
|
|
byte[] trailer = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");
|
|
requestStream.Write(trailer, 0, trailer.Length);
|
|
}
|
|
request.GetResponse().Dispose();
|
|
Information("File uploaded.");
|
|
}
|
|
|
|
Task("clean").IsDependentOn("RemoveTemporaries");
|
|
|
|
Task("Default").Does(()=>
|
|
{
|
|
Error("Please run a specific target.");
|
|
});
|
|
|
|
RunTarget(Target);
|