From 039e8e076da4dee002fcb6aabac0420ec6c9fa05 Mon Sep 17 00:00:00 2001 From: "Hunter T." Date: Mon, 1 Jun 2026 02:57:24 -0700 Subject: [PATCH 1/5] + Add script to automate compilation and creation of Cork app --- Scripts/build-self-compiled.sh | 278 +++++++++++++++++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100755 Scripts/build-self-compiled.sh diff --git a/Scripts/build-self-compiled.sh b/Scripts/build-self-compiled.sh new file mode 100755 index 00000000..3411dcd6 --- /dev/null +++ b/Scripts/build-self-compiled.sh @@ -0,0 +1,278 @@ +#!/usr/bin/env bash +# +# This script builds Cork's "Self-Compiled" scheme using xcodebuild without opening Xcode. +# +# Version: v1.0.0 +# License: MIT License +# Copyright (c) 2026 Hunter T. (StrangeRanger) +# +############################################################################################ +set -euo pipefail +####[ Global Variables ]#################################################################### + + +readonly C_GREEN=$'\033[0;32m' +readonly C_BLUE=$'\033[0;34m' +readonly C_CYAN=$'\033[0;36m' +readonly C_RED=$'\033[1;31m' +readonly C_NC=$'\033[0m' + +readonly C_ERROR="${C_RED}ERROR:${C_NC} " +readonly C_SUCC="${C_GREEN}==>${C_NC} " +readonly C_INFO="${C_BLUE}==>${C_NC} " +readonly C_NOTE="${C_CYAN}==>${C_NC} " + +C_ROOT_DIR="$(realpath -- "$(dirname -- "${BASH_SOURCE[0]}")/..")" +readonly C_ROOT_DIR +readonly C_BUILD_DIR="$C_ROOT_DIR/.build/self-compiled" +readonly C_DERIVED_DATA_DIR="$C_BUILD_DIR/DerivedData" +readonly C_SOURCE_PACKAGES_DIR="$C_BUILD_DIR/SourcePackages" +readonly C_PACKAGE_CACHE_DIR="$C_BUILD_DIR/PackageCache" +readonly C_ARCHIVE_PATH="$C_BUILD_DIR/Cork.xcarchive" +readonly C_ARCHIVED_APP="$C_ARCHIVE_PATH/Products/Applications/Cork.app" + +readonly C_SCHEME="Self-Compiled" +readonly C_CONFIGURATION="Release" + +install_dir="/Applications" +export_dir="$C_BUILD_DIR/export" +export_app="$export_dir/Cork.app" + +run_tuist=1 +clean=0 +install=0 +force=0 +launch=0 +xcodebuild_logging=(-quiet) + + +####[ Functions ]########################################################################### + + +#### +# Print script usage information. +usage() { + cat <${C_NC} Copy Cork.app to a different install directory + ${C_CYAN}--output-dir ${C_NC} Copy the built Cork.app to a different output directory + ${C_CYAN}--clean${C_NC} Remove this script's .build/self-compiled directory first + ${C_CYAN}--skip-tuist${C_NC} Skip 'tuist install' and 'tuist generate' + ${C_CYAN}--force${C_NC} Replace an existing installed app without prompting + ${C_CYAN}--launch${C_NC} Open the built or installed app after the build + ${C_CYAN}--verbose${C_NC} Show full xcodebuild output + ${C_CYAN}-h${C_NC}, ${C_CYAN}--help${C_NC} Show this help +EOF +} + +#### +# Print an error message and exits with a non-zero status. +std_error() { + local message="$*" + + echo "${C_ERROR}${message}" >&2 + exit 1 +} + +#### +# Check if a command is available in PATH. If not, prints an error and exits. +require_command() { + local cmd="$1" + + if ! command -v "$cmd" >/dev/null 2>&1; then + std_error "'$cmd' is required but was not found in PATH" + fi +} + +#### +# Ask the user to confirm replacing an existing app at the given path. Exits if the user +# cancels. +confirm_replace() { + local path="$1" + local answer + + if (( force == 1 )); then + return 0 + fi + + printf "%s'%s' already exists. Replace it? [y/N] " "$C_INFO" "$path" + read -r answer + + answer="${answer,,}" + case "$answer" in + y*) ;; + *) std_error "install cancelled" ;; + esac +} + +#### +# Guarantees that $C_BUILD_DIR is only deleted if it's under '$C_ROOT_DIR/.build'. This is a +# safety measure to prevent accidentally deleting important files if $C_BUILD_DIR is +# misconfigured. +clean_build_dir() { + case "$C_BUILD_DIR" in + "$C_ROOT_DIR"/.build/*) + rm -rf "$C_BUILD_DIR" + ;; + *) + std_error "INTERNAL: $C_BUILD_DIR is not under $C_ROOT_DIR/.build" + ;; + esac +} + + +####[ Argument Parsing ]#################################################################### + + +while (( $# > 0 )); do + case "$1" in + --install) + install=1 + ;; + --install-dir) + (( $# >= 2 )) || std_error "--install-dir requires a path" + install_dir="$2" + shift + ;; + --output-dir) + (( $# >= 2 )) || std_error "--output-dir requires a path" + export_dir="$2" + export_app="$export_dir/Cork.app" + shift + ;; + --clean) + clean=1 + ;; + --skip-tuist) + run_tuist=0 + ;; + --force) + force=1 + ;; + --launch) + launch=1 + ;; + --verbose) + xcodebuild_logging=() + ;; + -h|--help) + usage + exit 0 + ;; + *) + std_error "unknown option: $1" + ;; + esac + shift +done + +dest_app="$install_dir/Cork.app" +app_to_launch="$export_app" + + +####[ Pre-checks ]########################################################################## + + +case "$(uname -s)" in + Darwin) ;; + *) std_error "Cork can only be built on macOS" ;; +esac + +require_command xcodebuild +require_command xcrun +require_command ditto + +if (( run_tuist == 1 )); then + if ! command -v tuist &>/dev/null; then + std_error "Tuist is required to run this script with --skip-tuist." \ + "Please install Tuist and try again, or run the script with --skip-tuist if" \ + "you have already generated the Xcode project." + fi +fi + + +#####[ Main ]############################################################################### + + +if (( clean == 1 )); then + echo "${C_INFO}Cleaning '$C_BUILD_DIR'..." + clean_build_dir +fi + +cd "$C_ROOT_DIR" + +if (( run_tuist == 1 )); then + echo "${C_INFO}Installing Tuist dependencies..." + tuist install + + echo "${C_INFO}Generating Xcode project..." + if ! tuist generate --no-open --cache-profile none; then + echo "${C_INFO}Retrying Tuist generation with legacy cache flags..." + tuist generate --no-open --no-binary-cache + fi +fi + +[[ -d $C_ROOT_DIR/Cork.xcworkspace ]] || std_error "Cork.xcworkspace was not generated" + +echo "${C_INFO}Archiving '$C_SCHEME'..." + +xcodebuild \ + "${xcodebuild_logging[@]}" \ + -workspace "$C_ROOT_DIR/Cork.xcworkspace" \ + -scheme "$C_SCHEME" \ + -configuration "$C_CONFIGURATION" \ + -destination 'generic/platform=macOS' \ + -derivedDataPath "$C_DERIVED_DATA_DIR" \ + -clonedSourcePackagesDirPath "$C_SOURCE_PACKAGES_DIR" \ + -packageCachePath "$C_PACKAGE_CACHE_DIR" \ + -archivePath "$C_ARCHIVE_PATH" \ + -skipPackagePluginValidation \ + CODE_SIGN_STYLE=Manual \ + DEVELOPMENT_TEAM= \ + CODE_SIGN_IDENTITY=- \ + CODE_SIGNING_ALLOWED=YES \ + CODE_SIGNING_REQUIRED=YES \ + archive + +[[ -d $C_ARCHIVED_APP ]] || std_error "archive did not contain Cork.app at '$C_ARCHIVED_APP'" + +## Ensure export directory is empty before copying the app. +if [[ -e $export_app ]]; then + rm -rf "$export_app" +fi + +echo "${C_INFO}Copying app to '$export_app'..." +ditto "$C_ARCHIVED_APP" "$export_app" + +if (( install == 1 )); then + # Ensure the install directory exists before copying the app, especially if the user + # specified a custom directory with --install-dir. + mkdir -p "$install_dir" + + if [[ -e $dest_app ]]; then + confirm_replace "$dest_app" + rm -rf "$dest_app" + fi + + echo "${C_INFO}Installing app to '$dest_app'..." + ditto "$export_app" "$dest_app" + app_to_launch="$dest_app" +fi + +if (( launch == 1 )); then + echo "${C_INFO}Launching '$app_to_launch'..." + open "$app_to_launch" +fi + +echo "${C_SUCC}Done" +echo "${C_NOTE}Built app is at: $export_app" + +if (( install == 1 )); then + echo "${C_NOTE}Installed app is at: $dest_app" +fi + From dd20b3a5c3ea9502fd15e1159674f05fae843747 Mon Sep 17 00:00:00 2001 From: "Hunter T." Date: Mon, 1 Jun 2026 03:14:31 -0700 Subject: [PATCH 2/5] ~ Clarify Tuist not found error message --- Scripts/build-self-compiled.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Scripts/build-self-compiled.sh b/Scripts/build-self-compiled.sh index 3411dcd6..69e65256 100755 --- a/Scripts/build-self-compiled.sh +++ b/Scripts/build-self-compiled.sh @@ -189,7 +189,7 @@ require_command ditto if (( run_tuist == 1 )); then if ! command -v tuist &>/dev/null; then - std_error "Tuist is required to run this script with --skip-tuist." \ + std_error "Tuist is required to run this script, but it was not found in PATH." \ "Please install Tuist and try again, or run the script with --skip-tuist if" \ "you have already generated the Xcode project." fi @@ -275,4 +275,3 @@ echo "${C_NOTE}Built app is at: $export_app" if (( install == 1 )); then echo "${C_NOTE}Installed app is at: $dest_app" fi - From ecba6a151b521776ae20c271df2c85ae33c26d75 Mon Sep 17 00:00:00 2001 From: "Hunter T." Date: Mon, 1 Jun 2026 03:20:36 -0700 Subject: [PATCH 3/5] ~ Correct script usage example and comment --- Scripts/build-self-compiled.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Scripts/build-self-compiled.sh b/Scripts/build-self-compiled.sh index 69e65256..e2e81447 100755 --- a/Scripts/build-self-compiled.sh +++ b/Scripts/build-self-compiled.sh @@ -55,7 +55,7 @@ usage() { cat < Date: Mon, 1 Jun 2026 03:38:42 -0700 Subject: [PATCH 4/5] ~ Streamline command availability checks --- Scripts/build-self-compiled.sh | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/Scripts/build-self-compiled.sh b/Scripts/build-self-compiled.sh index e2e81447..10404771 100755 --- a/Scripts/build-self-compiled.sh +++ b/Scripts/build-self-compiled.sh @@ -79,16 +79,6 @@ std_error() { exit 1 } -#### -# Check if a command is available in PATH. If not, prints an error and exits. -require_command() { - local cmd="$1" - - if ! command -v "$cmd" >/dev/null 2>&1; then - std_error "'$cmd' is required but was not found in PATH" - fi -} - #### # Ask the user to confirm replacing an existing app at the given path. Exits if the user # cancels. @@ -183,9 +173,9 @@ case "$(uname -s)" in *) std_error "Cork can only be built on macOS" ;; esac -require_command xcodebuild -require_command xcrun -require_command ditto +if ! command -v xcodebuild &>/dev/null; then + std_error "xcodebuild is required to run this script, but it was not found in PATH." +fi if (( run_tuist == 1 )); then if ! command -v tuist &>/dev/null; then From 08d62ecdd9e9fb360ca08e1cdef053e93db8e5a8 Mon Sep 17 00:00:00 2001 From: "Hunter T." Date: Mon, 1 Jun 2026 11:40:29 -0700 Subject: [PATCH 5/5] ~ Rename self-compiled build script to build-cork-app --- Scripts/{build-self-compiled.sh => build-cork-app.bash} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename Scripts/{build-self-compiled.sh => build-cork-app.bash} (99%) diff --git a/Scripts/build-self-compiled.sh b/Scripts/build-cork-app.bash similarity index 99% rename from Scripts/build-self-compiled.sh rename to Scripts/build-cork-app.bash index 10404771..78db20e0 100755 --- a/Scripts/build-self-compiled.sh +++ b/Scripts/build-cork-app.bash @@ -66,7 +66,7 @@ ${C_BLUE}Options:${C_NC} ${C_CYAN}--force${C_NC} Replace an existing installed app without prompting ${C_CYAN}--launch${C_NC} Open the built or installed app after the build ${C_CYAN}--verbose${C_NC} Show full xcodebuild output - ${C_CYAN}-h${C_NC}, ${C_CYAN}--help${C_NC} Show this help + ${C_CYAN}-h${C_NC}, ${C_CYAN}--help${C_NC} Show this help message EOF }