Example #1
0
 protected async Task Sign(IEnumerable <string> paths, TaskToken task)
 {
     foreach (var path in paths)
     {
         await Sign(path, task);
     }
 }
Example #2
0
        protected async Task Sign(string path, TaskToken task, string entitlementsPath = null)
        {
            // Delete .meta files Unity might have erroneously copied to the build
            // and which will cause the signing to fail.
            // See: https://issuetracker.unity3d.com/issues/macos-standalone-build-contains-meta-files-inside-native-plugin-bundles
            if (Directory.Exists(path))
            {
                var metas = Directory.GetFiles(path, "*.meta", SearchOption.AllDirectories);
                foreach (var meta in metas)
                {
                    File.Delete(meta);
                }
            }

            var entitlements = "";

            if (entitlementsPath != null)
            {
                entitlements = $" --entitlements '{entitlementsPath}'";
            }

            var args = $"--force"
                       + $" --deep"
                       + $" --timestamp"
                       + $" --options=runtime"
                       + entitlements
                       + $" --sign '{appSignIdentity}'"
                       + $" '{path}'";

            await Execute(new ExecutionArgs("codesign", args), task);
        }
Example #3
0
        protected async Task <bool> Staple(string path, bool silentError, TaskToken task)
        {
            var args = $"stapler staple '{path}'";
            var code = await Execute(new ExecutionArgs("xcrun", args) { silentError = silentError }, task);

            return(code == 0);
        }
Example #4
0
        protected async Task <string> Upload(string path, TaskToken task)
        {
            var asc = "";

            if (!string.IsNullOrEmpty(ascProvider))
            {
                asc = $" --asc-provider '{ascProvider}'";
            }

            var args = $"altool"
                       + $" --notarize-app"
                       + $" --primary-bundle-id '{primaryBundleId}'"
                       + $" --username '{ascLogin.User}'"
                       + asc
                       + $" --file '{path}'";

            string requestUUID = null;

            await Execute(new ExecutionArgs("xcrun", args) {
                input    = ascLogin.GetPassword(keychainService) + "\n",
                onOutput = (output) => {
                    if (requestUUID != null)
                    {
                        return;
                    }
                    var match = RequestUUIDRegex.Match(output);
                    if (match.Success)
                    {
                        requestUUID = match.Groups[1].Value;
                    }
                }
            }, task);

            return(requestUUID);
        }
Example #5
0
        protected async Task Upload(BuildPath zipPath, TaskToken task)
        {
            var archive = zipPath.path;

            if (!File.Exists(archive))
            {
                throw new System.Exception("UploadDistro: Archive file does not exist: " + archive);
            }

            // Append a / to the url if necessary, otherwise curl treats the last part as a file name
            var url = uploadUrl;

            if (url[url.Length - 1] != '/')
            {
                url += "/";
            }

            string input = null;

            if (!string.IsNullOrEmpty(login.User))
            {
                input = string.Format("-u \"{0}:{1}\"", login.User, login.GetPassword(keychainService));
            }

            var arguments = string.Format(
                "-T '{0}' {1} --ssl -v '{2}'",
                archive, input != null ? "-K -" : "", url
                );

            task.Report(0, $"Uploading {Path.GetFileName(archive)} to {uploadUrl}");
            await Execute(new ExecutionArgs(curlPath, arguments) { input = input }, task);
        }
Example #6
0
        protected async Task Zip(string input, string output, TaskToken task)
        {
            var args = $" -qr"
                       + $" '{output}'"
                       + $" '{input}'";

            await Execute(new ExecutionArgs("zip", args), task);
        }
Example #7
0
        /// <summary>
        /// Run the distribution as an async task,
        /// since async tasks can't survive domain reloads,
        /// this method cannot build and will error if any builds are missing.
        /// </summary>
        public async Task DistributeWithoutBuilding(TaskToken task = default)
        {
            var removeProgressTask = false;

            if (task.taskId == 0)
            {
                // Set up default cancelable progress task if none is given
                removeProgressTask = true;

                task.taskId = Progress.Start(name);

                var source = new CancellationTokenSource();
                task.cancellation = source.Token;

                Progress.RegisterCancelCallback(task.taskId, () => {
                    source.Cancel();
                    return(true);
                });
            }

            try {
                // Prevent domain reloads from stopping the distribution
                EditorApplication.LockReloadAssemblies();

                // Check that all builds are present
                var paths = GetBuildPaths();
                if (!paths.Any())
                {
                    throw new Exception(name + ": Distribution has no targets in any of its profiles");
                }

                var missingBuilds = false;
                foreach (var path in paths)
                {
                    if (path.path == null)
                    {
                        Debug.LogError(name + ": Missing build for target '" + path.target + "' of profile '" + path.profile + "'");
                        missingBuilds = true;
                    }
                }
                if (missingBuilds)
                {
                    throw new Exception($"{name}: Missing builds");
                }

                // Run distribution
                await RunDistribute(paths, task);
            } finally {
                EditorApplication.UnlockReloadAssemblies();

                if (removeProgressTask)
                {
                    task.Remove();
                }
            }
        }
Example #8
0
 /// <summary>
 /// Notarize a macOS build.
 /// </summary>
 /// <remarks>
 /// This method will silently ignore non-macOS builds.
 /// </remarks>
 /// <param name="buildPath">Build path</param>
 public async Task NotarizeIfMac(BuildPath buildPath, TaskToken task)
 {
     if (buildPath.target == BuildTarget.StandaloneOSX)
     {
         var child = task.StartChild("Notarize macOS Build");
         try {
             await Notarize(buildPath, child);
         } finally {
             child.Remove();
         }
     }
 }
Example #9
0
        protected async Task Upload(string archivePath, string exportOptionsPlist, TaskToken task)
        {
            var args = $"-exportArchive"
                       + $" -archivePath '{archivePath}'"
                       + $" -exportOptionsPlist '{exportOptionsPlist}'";

            if (allowProvisioningUpdates)
            {
                args += " -allowProvisioningUpdates";
            }

            await Execute(new ExecutionArgs("xcodebuild", args), task);
        }
Example #10
0
        protected async Task Process(string path, TaskToken task)
        {
            task.Report(0, 2);

            // Create archive
            var projectPath = Path.Combine(path, "Unity-iPhone.xcodeproj");

            if (!Directory.Exists(projectPath))
            {
                throw new Exception($"iOSDistro: Could not find Xcode project at path '{projectPath}'");
            }

            var archiveName = $"{scheme}-{PlayerSettings.iOS.buildNumber}-{DateTime.Now.ToString("O")}.xcarchive";
            var archivePath = Path.Combine(archivesPath, archiveName);

            task.Report(0, description: $"Building scheme '{scheme}'");
            await Archive(projectPath, scheme, archivePath, task);

            // Upload archive
            var    cleanUpOptions    = false;
            string exportOptionsPath = null;

            try {
                if (exportOptions != null)
                {
                    exportOptionsPath = AssetDatabase.GetAssetPath(exportOptions);
                }

                if (string.IsNullOrEmpty(exportOptionsPath))
                {
                    cleanUpOptions    = true;
                    exportOptionsPath = Path.GetTempFileName();
                    File.WriteAllText(exportOptionsPath, DefaultExportOptions);
                }

                task.Report(1, description: $"Uploading archive");
                await Upload(archivePath, exportOptionsPath, task);
            } finally {
                if (cleanUpOptions)
                {
                    File.Delete(exportOptionsPath);
                }
            }
        }
Example #11
0
        /// <summary>
        /// Async wrapper for OptionHelper.RunScriptAsync.
        /// </summary>
        protected async Task <int> Execute(ExecutionArgs args, TaskToken task)
        {
            var outputBuilder = new StringBuilder();
            var errorBuilder  = new StringBuilder();
            int?exitcode      = null;
            var terminator    = OptionHelper.RunScriptAsnyc(
                args.startInfo, args.input,
                (output) => {
                outputBuilder.AppendLine(output);
                args.onOutput?.Invoke(output);
            },
                (error) => {
                errorBuilder.AppendLine(error);
                args.onError?.Invoke(error);
            },
                (code) => {
                exitcode = code;
            }
                );

            while (exitcode == null)
            {
                if (task.cancellation.IsCancellationRequested)
                {
                    terminator(true);
                    task.ThrowIfCancellationRequested();
                }
                await Task.Yield();
            }

            // 137 happens for Kill() and 143 for CloseMainWindow(),
            // which means the script has been canceled
            if (!args.silentError && exitcode != 0 && exitcode != 137 && exitcode != 143)
            {
                throw new Exception(string.Format(
                                        "{0}: Failed to execute {1} (code {2}): {3}\nOutput: {4}",
                                        name, Path.GetFileName(args.startInfo.FileName), exitcode,
                                        errorBuilder.ToString(), outputBuilder.ToString()
                                        ));
            }

            return(exitcode.Value);
        }
Example #12
0
        protected async Task <string> WaitForCompletion(string requestUUID, TaskToken task)
        {
            string status = null, logFile = null;

            do
            {
                await Task.Delay(TimeSpan.FromSeconds(statusCheckInterval));

                var args = $"altool"
                           + $" --notarization-info '{requestUUID}'"
                           + $" --username '{ascLogin.User}'";

                status = null;
                await Execute(new ExecutionArgs("xcrun", args) {
                    input    = ascLogin.GetPassword(keychainService) + "\n",
                    onOutput = (output) => {
                        if (status == null)
                        {
                            var match = StatusRegex.Match(output);
                            if (match.Success)
                            {
                                status = match.Groups[1].Value;
                            }
                        }
                        if (logFile == null)
                        {
                            var match = LogFileRegex.Match(output);
                            if (match.Success)
                            {
                                logFile = match.Groups[1].Value;
                            }
                        }
                    }
                }, task);
            } while (status == "in progress");

            if (logFile != null)
            {
                Debug.Log("Notarization log file: " + logFile);
            }

            return(status);
        }
Example #13
0
        protected async Task Upload(string path, TaskToken task)
        {
            var asc = "";

            if (!string.IsNullOrEmpty(ascProvider))
            {
                asc = $" --asc-provider '{ascProvider}'";
            }

            var args = $"altool"
                       + $" --upload-app"
                       + $" --type osx"
                       + $" --username '{ascLogin.User}'"
                       + asc
                       + $" --file '{path}'";

            await Execute(new ExecutionArgs("xcrun", args) {
                input = ascLogin.GetPassword(keychainService) + "\n"
            }, task);
        }
Example #14
0
        public void Run(Job[] jobs, bool restoreActiveBuildTarget = true, UnityEngine.Object context = null)
        {
            if (jobs == null || jobs.Length == 0)
            {
                DestroyImmediate(this);
                throw new Exception($"Trimmer BuildRunner: No jobs given.");
            }

            EnsureNotRunning();

            var results = new ProfileBuildResult[jobs.Length];

            for (int i = 0; i < jobs.Length; i++)
            {
                var job = jobs[i];
                if ((job.profile == null || job.target == 0 || job.target == BuildTarget.NoTarget) && job.distro == null)
                {
                    DestroyImmediate(this);
                    throw new Exception($"Trimmer BuildRunner: Invalid job at index {i}: Profile or target or distro not set ({job.profile} / {job.target} / {job.distro})");
                }
                results[i].profile = job.profile;
            }

            Current               = this;
            this.jobs             = jobs;
            this.results          = results;
            restoreActiveTargetTo = EditorUserBuildSettings.activeBuildTarget;
            jobIndex              = -1;

            token         = TaskToken.Start(context?.name ?? "Trimmer", options: Progress.Options.Synchronous);
            token.context = context;
            Progress.SetPriority(token.taskId, Progress.Priority.High);
            token.Report(0, jobs.Length);

            //Debug.Log($"Trimmer BuildRunner: Got jobs:\n{string.Join("\n", jobs.Select(j => $"- {j.profile?.name ?? "<none>"} {j.target}"))}");

            ContinueWith(ContinueTask.NextJob);
        }
Example #15
0
        async Task Distribute(BuildPath buildPath, TaskToken task)
        {
            task.Report(0, description: $"Pushing {buildPath.target}");

            var path = OptionHelper.GetBuildBasePath(buildPath.path);

            var channel = ChannelNames[buildPath.target];

            if (!string.IsNullOrEmpty(channelSuffix))
            {
                channel += "-" + channelSuffix;
            }

            var version = Application.version;

            var buildInfo = BuildInfo.FromPath(path);

            if (buildInfo != null)
            {
                if (!buildInfo.version.IsDefined)
                {
                    Debug.LogWarning("ItchDistro: build.json exists but contains no version.");
                }
                else
                {
                    version = buildInfo.version.MajorMinorPatchBuild;
                }
            }

            var args = string.Format(
                "push '{0}' '{1}:{2}' --userversion '{3}' --ignore='*.DS_Store' --ignore='build.json'",
                path, project, channel, Application.version
                );

            await Execute(new ExecutionArgs(butlerPath, args), task);
        }
Example #16
0
        protected override async Task RunDistribute(IEnumerable <BuildPath> buildPaths, TaskToken task)
        {
            if (string.IsNullOrEmpty(curlPath))
            {
                throw new Exception("UploadDistro: Path to curl not set.");
            }

            if (!File.Exists(curlPath))
            {
                throw new Exception("UploadDistro: curl not found at path: " + curlPath);
            }

            if (string.IsNullOrEmpty(uploadUrl))
            {
                throw new Exception("UploadDistro: No upload URL set.");
            }

            if (!string.IsNullOrEmpty(login.User) && login.GetPassword(keychainService) == null)
            {
                throw new Exception("UploadDistro: No password set for user: "******"Archiving builds");

            IEnumerable <BuildPath> zipPaths;
            var child = task.StartChild("Zip Builds");

            try {
                zipPaths = await ZipBuilds(buildPaths, child);
            } finally {
                child.Remove();
            }

            task.Report(1, description: "Uploading builds");

            child = task.StartChild("Upload Builds");
            try {
                foreach (var path in zipPaths)
                {
                    await Upload(path, child);
                }
            } finally {
                child.Remove();
            }
        }
Example #17
0
        protected async Task <IEnumerable <BuildPath> > ZipBuilds(IEnumerable <BuildPath> buildPaths, TaskToken task)
        {
            var queue   = new Queue <BuildPath>(buildPaths);
            var results = new List <BuildPath>();

            task.Report(0, queue.Count);

            while (queue.Count > 0)
            {
                var next = queue.Dequeue();

                if (macNotarization != null)
                {
                    await macNotarization.NotarizeIfMac(next, task);
                }

                results.Add(await Zip(next, task));

                task.baseStep++;
            }

            return(results);
        }
Example #18
0
 protected override async Task RunDistribute(IEnumerable <BuildPath> buildPaths, TaskToken task)
 {
     foreach (var buildPath in buildPaths)
     {
         if (buildPath.target != BuildTarget.StandaloneOSX)
         {
             continue;
         }
         await Notarize(buildPath, task);
     }
 }
Example #19
0
        protected override async Task RunDistribute(IEnumerable <BuildPath> buildPaths, TaskToken task)
        {
            // Check Pipeline Builder Executable
            var cmd = FindPipelineBuilder();

            // Check User
            if (string.IsNullOrEmpty(gogLogin.User))
            {
                throw new Exception("GOGDistro: No GOG user set.");
            }

            if (gogLogin.GetPassword(keychainService) == null)
            {
                throw new Exception("GOGDistro: No GOG password found in Keychain.");
            }

            // Check projects
            if (string.IsNullOrEmpty(projectsFolder) || !Directory.Exists(projectsFolder))
            {
                throw new Exception("GOGDistro: Path to projects folder not set.");
            }

            // Check ignore list
            if (!string.IsNullOrEmpty(ignoreList) && !File.Exists(ignoreList))
            {
                throw new Exception("GOGDistro: Ignore list could not be found: " + ignoreList);
            }

            // Process projects
            var tempDir = FileUtil.GetUniqueTempPathInProject();

            try {
                Directory.CreateDirectory(tempDir);

                var    targets      = new HashSet <BuildTarget>(buildPaths.Select(p => p.target));
                var    projects     = new List <string>();
                string convertError = null;
                foreach (var file in Directory.GetFiles(projectsFolder))
                {
                    if (Path.GetExtension(file).ToLower() != ".json")
                    {
                        continue;
                    }

                    var contents = PathVarRegex.Replace(File.ReadAllText(file), (match) => {
                        var platformName = match.Groups[1].Value.ToLower();

                        if (platformName == "project")
                        {
                            return(Path.GetDirectoryName(Application.dataPath));
                        }
                        else if (platformName == "projects")
                        {
                            return(Path.GetFullPath(projectsFolder));
                        }

                        BuildTarget target;
                        try {
                            target = (BuildTarget)System.Enum.Parse(typeof(BuildTarget), platformName, true);
                        } catch {
                            convertError = $"Invalid build target path variable '{platformName}' in project JSON: {file}";
                            return("");
                        }

                        if (!buildPaths.Any(p => p.target == target))
                        {
                            convertError = $"Build target '{platformName}' not part of given build profile(s) in project JSON: {file}";
                            return("");
                        }
                        targets.Remove(target);

                        var path = buildPaths.Where(p => p.target == target).Select(p => p.path).First();
                        path     = OptionHelper.GetBuildBasePath(path);

                        return(Path.GetFullPath(path));
                    });
                    if (convertError != null)
                    {
                        break;
                    }

                    var targetPath = Path.Combine(tempDir, Path.GetFileName(file));
                    File.WriteAllText(targetPath, contents);
                    projects.Add(targetPath);
                }

                if (convertError != null)
                {
                    throw new Exception($"GOGDistro: {convertError}");
                }

                if (targets.Count > 0)
                {
                    Debug.LogWarning("GOGDistro: Not all build targets filled into variables. Left over: "
                                     + string.Join(", ", targets.Select(t => t.ToString()).ToArray()));
                }

                task.Report(0, targets.Count + 1);

                // Notarize mac builds
                if (macNotarization != null)
                {
                    task.Report(0, description: "Notarizing macOS builds");
                    foreach (var path in buildPaths.Where(p => p.target == BuildTarget.StandaloneOSX))
                    {
                        await macNotarization.Notarize(path, task);
                    }
                }

                // Build
                task.baseStep++;
                foreach (var project in projects)
                {
                    var args = string.Format(
                        "build-game '{0}' --username='******' --password='******' --version={3}",
                        Path.GetFullPath(project), gogLogin.User, gogLogin.GetPassword(keychainService),
                        string.IsNullOrEmpty(overrideVersion) ? Application.version : overrideVersion
                        );

                    if (!string.IsNullOrEmpty(ignoreList))
                    {
                        args += $" --ignore_list='{Path.GetFullPath(ignoreList)}'";
                    }

                    if (!string.IsNullOrEmpty(branch.User))
                    {
                        args += $" --branch='{branch.User}'";

                        var pwd = branch.GetPassword(branchKeychainService);
                        if (pwd != null)
                        {
                            args += $" --branch_password='******'";
                        }
                    }

                    task.Report(0, description: $"Uploading {Path.GetFileName(project)}");

                    await Execute(new ExecutionArgs(cmd, args), task);

                    task.baseStep++;
                }
            } finally {
                // Always clean up temporary files
                Directory.Delete(tempDir, true);
            }
        }
Example #20
0
        protected override async Task RunDistribute(IEnumerable <BuildPath> buildPaths, TaskToken task)
        {
            var hasMacBuild = false;

            foreach (var buildPath in buildPaths)
            {
                if (buildPath.target != BuildTarget.StandaloneOSX)
                {
                    continue;
                }

                hasMacBuild = true;
                await Process(buildPath.path, task);
            }

            if (!hasMacBuild)
            {
                throw new Exception("MASDistro: No macOS build in profiles");
            }
        }
Example #21
0
        protected async Task Process(string path, TaskToken task)
        {
            // Check settings
            if (string.IsNullOrEmpty(appSignIdentity))
            {
                throw new Exception("MASDistro: App sign identity not set.");
            }

            if (entitlements == null)
            {
                throw new Exception("MASDistro: Entitlements file not set.");
            }

            if (provisioningProfile == null)
            {
                throw new Exception("MASDistro: Provisioning profile not set.");
            }

            if (linkFrameworks != null && linkFrameworks.Length > 0 && !File.Exists(optoolPath))
            {
                throw new Exception("MASDistro: optool path not set for linking frameworks.");
            }

            var plistPath = Path.Combine(path, "Contents/Info.plist");

            if (!File.Exists(plistPath))
            {
                throw new Exception("MASDistro: Info.plist file not found at path: " + plistPath);
            }

            task.Report(0, 3);

            var doc = new PlistDocument();

            doc.ReadFromFile(plistPath);

            // Edit Info.plist
            if (!string.IsNullOrEmpty(copyright) || !string.IsNullOrEmpty(languages))
            {
                if (!string.IsNullOrEmpty(copyright))
                {
                    doc.root.SetString("NSHumanReadableCopyright", string.Format(copyright, System.DateTime.Now.Year));
                }

                if (!string.IsNullOrEmpty(languages))
                {
                    var parts = languages.Split(',');

                    var array = doc.root.CreateArray("CFBundleLocalizations");
                    foreach (var part in parts)
                    {
                        array.AddString(part.Trim());
                    }
                }

                doc.WriteToFile(plistPath);
            }

            // Link frameworks
            if (linkFrameworks != null && linkFrameworks.Length > 0)
            {
                task.Report(0, description: "Linking frameworks");

                var binaryPath = Path.Combine(path, "Contents/MacOS");
                binaryPath = Path.Combine(binaryPath, doc.root["CFBundleExecutable"].AsString());

                foreach (var framework in linkFrameworks)
                {
                    var frameworkBinaryPath = FindFramework(framework);
                    if (frameworkBinaryPath == null)
                    {
                        throw new Exception("MASDistro: Could not locate framework: " + framework);
                    }

                    var otoolargs = string.Format(
                        "install -c weak -p '{0}' -t '{1}'",
                        frameworkBinaryPath, binaryPath
                        );
                    await Execute(new ExecutionArgs(optoolPath, otoolargs), task);
                }
            }

            // Copy provisioning profile
            var profilePath  = AssetDatabase.GetAssetPath(provisioningProfile);
            var embeddedPath = Path.Combine(path, "Contents/embedded.provisionprofile");

            File.Copy(profilePath, embeddedPath, true);

            // Sign plugins
            var plugins = Path.Combine(path, "Contents/Plugins");

            if (Directory.Exists(plugins))
            {
                task.Report(0, description: "Signing plugins");
                await Sign(Directory.GetFiles(plugins, "*.dylib", SearchOption.AllDirectories), task);
                await Sign(Directory.GetFiles(plugins, "*.bundle", SearchOption.AllDirectories), task);
                await Sign(Directory.GetDirectories(plugins, "*.bundle", SearchOption.TopDirectoryOnly), task);
            }

            // Sign application
            task.Report(1, description: "Singing app");
            var entitlementsPath = AssetDatabase.GetAssetPath(entitlements);

            await Sign(path, task, entitlementsPath);

            // Create installer
            var pkgPath = Path.ChangeExtension(path, ".pkg");

            if (!string.IsNullOrEmpty(installerSignIdentity))
            {
                task.Report(1, description: "Creating installer");
                var args = string.Format(
                    "--component '{0}' /Applications --sign '{1}' '{2}'",
                    path, installerSignIdentity, pkgPath
                    );
                await Execute(new ExecutionArgs("productbuild", args), task);
            }

            // Upload to App Store
            if (!string.IsNullOrEmpty(ascLogin.User))
            {
                task.Report(2, description: "Uploading to App Store Connect");
                await Upload(pkgPath, task);
            }
        }
Example #22
0
        protected override async Task RunDistribute(IEnumerable <BuildPath> buildPaths, TaskToken task)
        {
            var hasTarget = false;

            foreach (var buildPath in buildPaths)
            {
                if (buildPath.target != BuildTarget.iOS)
                {
                    continue;
                }

                hasTarget = true;
                await Process(buildPath.path, task);
            }

            if (!hasTarget)
            {
                throw new Exception($"iOSDistro: No iOS build target in build profiles");
            }
        }
Example #23
0
        protected async Task Archive(string projectPath, string schemeName, string archivePath, TaskToken task)
        {
            var args = $"archive"
                       + $" -project '{projectPath}'"
                       + $" -archivePath '{archivePath}'"
                       + $" -scheme '{schemeName}'";

            if (allowProvisioningUpdates)
            {
                args += " -allowProvisioningUpdates";
            }

            await Execute(new ExecutionArgs("xcodebuild", args), task);
        }
Example #24
0
        protected override async Task RunDistribute(IEnumerable <BuildPath> buildPaths, TaskToken task)
        {
            if (string.IsNullOrEmpty(scriptPath))
            {
                throw new Exception("ScriptDistro: Script path not set.");
            }

            if (individual)
            {
                task.Report(0, buildPaths.Count());
                foreach (var buildPath in buildPaths)
                {
                    task.Report(0, description: $"Running script {Path.GetFileName(scriptPath)} for {buildPath.target}");
                    var args = ReplaceVariablesIndividual(arguments, buildPath);
                    await Execute(new ExecutionArgs(scriptPath, args), task);

                    task.baseStep++;
                }
            }
            else
            {
                task.Report(0, 1, $"Running script {Path.GetFileName(scriptPath)}");
                var args = ReplaceVariables(arguments, buildPaths);
                await Execute(new ExecutionArgs(scriptPath, args), task);
            }
        }
Example #25
0
 /// <summary>
 /// Subroutine to override in subclasses to do the actual processing.
 /// </summary>
 /// <param name="buildPaths">Build paths of the linked Build Profiles</param>
 /// <param name="task">Task token for reporting progress and cancellation</param>
 protected virtual Task RunDistribute(IEnumerable <BuildPath> buildPaths, TaskToken task)
 {
     return(Task.CompletedTask);
 }
Example #26
0
        /// <summary>
        /// Notarize a macOS build.
        /// </summary>
        /// <remarks>
        /// This method will throw if the given build is not a macOS build.
        /// </remarks>
        /// <param name="macBuildPath">Path to the app bundle</param>
        public async Task Notarize(BuildPath macBuildPath, TaskToken task)
        {
            if (macBuildPath.target != BuildTarget.StandaloneOSX)
            {
                throw new Exception($"NotarizationDistro: Notarization is only available for macOS builds (got {macBuildPath.target})");
            }

            var path = macBuildPath.path;

            // Check settings
            if (string.IsNullOrEmpty(appSignIdentity))
            {
                throw new Exception("NotarizationDistro: App sign identity not set.");
            }

            if (entitlements == null)
            {
                throw new Exception("NotarizationDistro: Entitlements file not set.");
            }

            // Check User
            if (string.IsNullOrEmpty(ascLogin.User))
            {
                throw new Exception("NotarizationDistro: No App Store Connect user set.");
            }

            if (ascLogin.GetPassword(keychainService) == null)
            {
                throw new Exception("NotarizationDistro: No App Store Connect password found in Keychain.");
            }

            task.Report(0, 6, "Checking if app is already notarized");

            // Try stapling in case the build has already been notarized
            if (await Staple(path, silentError: true, task))
            {
                Debug.Log("Build already notarized, nothing more to do...");
                return;
            }

            task.Report(1, description: "Signing app");

            // Sign plugins
            // codesign --deep --force does not resign nested plugins,
            // --force only applies to the main bundle. If we want to
            // resign nested plugins, we have to call codesign for each.
            // This is required for library validation with the hardened runtime.
            var plugins = Path.Combine(path, "Contents/Plugins");

            if (Directory.Exists(plugins))
            {
                await Sign(Directory.GetFiles(plugins, "*.dylib", SearchOption.TopDirectoryOnly), task);
                await Sign(Directory.GetFiles(plugins, "*.bundle", SearchOption.TopDirectoryOnly), task);
                await Sign(Directory.GetDirectories(plugins, "*.bundle", SearchOption.TopDirectoryOnly), task);
            }

            // Sign application
            var entitlementsPath = AssetDatabase.GetAssetPath(entitlements);

            await Sign(path, task, entitlementsPath);

            task.Report(2, description: "Zipping app");

            // Zip app
            var zipPath = path + ".zip";

            await Zip(path, zipPath, task);

            task.Report(3, description: "Uploading app");

            // Upload for notarization
            string requestUUID = null;

            try {
                requestUUID = await Upload(zipPath, task);

                if (requestUUID == null)
                {
                    throw new Exception("NotarizationDistro: Could not parse request UUID from upload output");
                }
            } finally {
                // Delete ZIP regardless of upload result
                File.Delete(zipPath);
            }

            task.Report(4, description: "Waiting for notarization result");

            // Wait for notarization to complete
            var status = await WaitForCompletion(requestUUID, task);

            if (status != "success")
            {
                throw new Exception($"NotarizationDistro: Got '{status}' notarization status");
            }

            task.Report(5, description: "Stapling ticket to app");

            // Staple
            await Staple(path, silentError : false, task);
        }
Example #27
0
 protected override async Task RunDistribute(IEnumerable <BuildPath> buildPaths, TaskToken task)
 {
     await ZipBuilds(buildPaths, task);
 }
Example #28
0
        protected async Task RenameRoot(string archivePath, string oldName, string newName, TaskToken task)
        {
            if (!File.Exists(archivePath))
            {
                throw new Exception("ZipDistro: Path to archive does not exist: " + archivePath);
            }

            var args = string.Format(
                "rn '{0}' '{1}' '{2}'",
                archivePath, oldName, newName
                );

            await Execute(new ExecutionArgs(Get7ZipPath(), args), task);
        }
Example #29
0
        protected async Task <BuildPath> Zip(BuildPath buildPath, TaskToken task)
        {
            var target = buildPath.target;
            var path   = buildPath.path;

            if (!File.Exists(path) && !Directory.Exists(path))
            {
                throw new Exception("ZipDistro: Path to compress does not exist: " + path);
            }

            if (ZipIgnorePatterns == null)
            {
                ZipIgnorePatterns = new Regex[ZipIgnore.Length];
                for (int i = 0; i < ZipIgnore.Length; i++)
                {
                    var regex = Regex.Escape(ZipIgnore[i]).Replace(@"\*", ".*").Replace(@"\?", ".");
                    ZipIgnorePatterns[i] = new Regex(regex);
                }
            }

            var sevenZPath = Get7ZipPath();

            // Path can point to executable file but there might be files
            // in the containing directory we need as well
            var basePath = OptionHelper.GetBuildBasePath(path);

            // Check the files in containing directory
            var files = new List <string>(Directory.GetFileSystemEntries(basePath));

            for (int i = files.Count - 1; i >= 0; i--)
            {
                var filename = Path.GetFileName(files[i]);
                foreach (var pattern in ZipIgnorePatterns)
                {
                    if (pattern.IsMatch(filename))
                    {
                        files.RemoveAt(i);
                        goto ContinueOuter;
                    }
                }
                ContinueOuter :;
            }

            if (files.Count == 0)
            {
                throw new Exception("ZipDistro: Nothing to ZIP in directory: " + basePath);
            }

            // Determine output path first to make it consistent and use absolute path
            // since the script will be run in a different working directory
            var prettyName = GetPrettyName(target);

            if (prettyName == null)
            {
                prettyName = Path.GetFileNameWithoutExtension(basePath);
            }

            var versionSuffix = "";

            if (appendVersion)
            {
                var buildInfo = BuildInfo.FromPath(path);
                if (buildInfo != null)
                {
                    if (!buildInfo.version.IsDefined)
                    {
                        Debug.LogWarning("ZipDistro: build.json exists but contains no version");
                    }
                    else
                    {
                        versionSuffix = " " + buildInfo.version.MajorMinorPatch;
                    }
                }

                if (versionSuffix.Length == 0)
                {
                    versionSuffix = " " + Application.version;
                }
            }

            var extension = FileExtensions[(int)format];
            var zipName   = prettyName + versionSuffix + extension;

            zipName = zipName.Replace(" ", "_");

            var outputPath = Path.Combine(Path.GetDirectoryName(basePath), zipName);

            outputPath = Path.GetFullPath(outputPath);

            // Delete existing archive, otherwise 7za will update it
            if (File.Exists(outputPath))
            {
                File.Delete(outputPath);
            }

            // In case it only contains a single file, just zip that file
            var singleFile = false;

            if (files.Count == 1)
            {
                singleFile = true;
                basePath   = files[0];
            }

            // Run 7za command to create ZIP file
            var excludes = "";

            foreach (var pattern in ZipIgnore)
            {
                excludes += @" -xr\!'" + pattern + "'";
            }

            var inputName = Path.GetFileName(basePath);
            var args      = string.Format(
                "a '{0}' '{1}' -r -mx{2} {3}",
                outputPath, inputName, (int)compression, excludes
                );

            var startInfo = new System.Diagnostics.ProcessStartInfo();

            startInfo.FileName         = sevenZPath;
            startInfo.Arguments        = args;
            startInfo.WorkingDirectory = Path.GetDirectoryName(basePath);

            task.Report(0, description: $"Archiving {inputName}");

            await Execute(new ExecutionArgs()
            {
                startInfo = startInfo
            }, task);

            if (!singleFile && prettyName != inputName)
            {
                await RenameRoot(outputPath, inputName, prettyName, task);
            }

            return(new BuildPath(buildPath.profile, target, outputPath));
        }
Example #30
0
        protected override async Task RunDistribute(IEnumerable <BuildPath> buildPaths, TaskToken task)
        {
            // Check SDK
            var cmd = FindSteamCmd();

            // Check User
            if (string.IsNullOrEmpty(steamLogin.User))
            {
                throw new Exception("SteamDistro: No Steam user set.");
            }

            if (steamLogin.GetPassword(keychainService) == null)
            {
                throw new Exception("SteamDistro: No Steam password found in Keychain.");
            }

            // Check scripts
            if (string.IsNullOrEmpty(scriptsFolder) || !Directory.Exists(scriptsFolder))
            {
                throw new Exception("SteamDistro: Path to scripts folder not set.");
            }

            if (string.IsNullOrEmpty(appScript))
            {
                throw new Exception("SteamDistro: Name of app script not set.");
            }

            var appScriptPath = Path.Combine(scriptsFolder, appScript);

            if (!File.Exists(appScriptPath))
            {
                throw new Exception("SteamDistro: App script not found in scripts folder.");
            }

            // Process scripts
            var tempDir = FileUtil.GetUniqueTempPathInProject();

            try {
                Directory.CreateDirectory(tempDir);

                var    targets      = new HashSet <BuildTarget>(buildPaths.Select(p => p.target));
                string convertError = null;
                foreach (var file in Directory.GetFiles(scriptsFolder))
                {
                    if (Path.GetExtension(file).ToLower() != ".vdf")
                    {
                        continue;
                    }

                    var contents = PathVarRegex.Replace(File.ReadAllText(file), (match) => {
                        var platformName = match.Groups[1].Value.ToLower();

                        if (platformName == "project")
                        {
                            return(Path.GetDirectoryName(Application.dataPath));
                        }
                        else if (platformName == "scripts")
                        {
                            return(Path.GetFullPath(scriptsFolder));
                        }

                        BuildTarget target;
                        try {
                            target = (BuildTarget)System.Enum.Parse(typeof(BuildTarget), platformName, true);
                        } catch {
                            convertError = $"SteamDistro: Invalid build target path variable '{platformName}' in VDF script: {file}";
                            return("");
                        }

                        if (!buildPaths.Any(p => p.target == target))
                        {
                            convertError = $"SteamDistro: Build target '{platformName}' not part of given build profile(s) in VDF script: {file}";
                            return("");
                        }
                        targets.Remove(target);

                        var path = buildPaths.Where(p => p.target == target).Select(p => p.path).First();
                        path     = OptionHelper.GetBuildBasePath(path);

                        return(Path.GetFullPath(path));
                    });
                    if (convertError != null)
                    {
                        break;
                    }

                    var targetPath = Path.Combine(tempDir, Path.GetFileName(file));
                    File.WriteAllText(targetPath, contents);
                }

                if (convertError != null)
                {
                    throw new Exception($"SteamDistro: {convertError}");
                }

                if (targets.Count > 0)
                {
                    Debug.LogWarning("SteamDistro: Not all build targets filled into variables. Left over: "
                                     + string.Join(", ", targets.Select(t => t.ToString()).ToArray()));
                }

                // Notarize mac builds
                if (macNotarization != null)
                {
                    task.Report(0, description: "Notarizing macOS builds");
                    foreach (var path in buildPaths.Where(p => p.target == BuildTarget.StandaloneOSX))
                    {
                        await macNotarization.Notarize(path, task);
                    }
                }

                // Build
                var scriptPath = Path.GetFullPath(Path.Combine(tempDir, appScript));
                var args       = string.Format(
                    "+login '{0}' '{1}' +run_app_build_http '{2}' +quit",
                    steamLogin.User, steamLogin.GetPassword(keychainService), scriptPath
                    );

                await Execute(new ExecutionArgs(cmd, args) {
                    onOutput = (output) => {
                        if (output.Contains("Logged in OK"))
                        {
                            task.Report(0, description: "Logged in");
                        }
                        else if (output.Contains("Building depot"))
                        {
                            var match = BuildingDepotRegex.Match(output);
                            if (match.Success)
                            {
                                task.Report(0, description: $"Building depo {match.Groups[1].Value}");
                            }
                        }
                        else if (output.Contains(""))
                        {
                            var match = SuccessBuildIdRegex.Match(output);
                            if (match.Success)
                            {
                                Debug.Log("SteamDistro: Build uploaded, ID = " + match.Groups[1].Value);
                            }
                        }
                    }
                }, task);
            } finally {
                // Always clean up temp files
                Directory.Delete(tempDir, true);
            }
        }