diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 70e2488..e8c39c7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,65 +1,32 @@ name: Build on: - push: - branches: ['main'] - pull_request: - branches: ['main'] - create: - tags: ['v[0-9]+.[0-9]+.[0-9]+'] + push: { branches: ['main'] } + pull_request: { branches: ['main'] } jobs: build: name: Xcode Build - runs-on: macos-latest + runs-on: macos-12 steps: - - name: Checkout repository - uses: actions/checkout@v3 + - name: Select Xcode Version + run: sudo xcrun xcode-select -s /Applications/Xcode_13.4.app - - name: Set Build Version - if: ${{ github.ref_type == 'tag' }} - run: 'agvtool new-marketing-version "${GITHUB_REF_NAME#v}" && agvtool new-version -all "$GITHUB_RUN_NUMBER"' + - name: Set Environment Variables + run: 'echo "XCARCHIVE_PATH=${PWD}/BeezyLight.xcarchive" >> $GITHUB_ENV' - - name: Run xcodebuild - run: 'xcodebuild -workspace ./BeezyLight.xcodeproj/project.xcworkspace -scheme BeezyLight -configuration Release -destination "generic/platform=macOS" -archivePath "$PWD/build/Release/BeezyLight.xcarchive" archive' + - name: Checkout repository + uses: actions/checkout@v3 - - name: Compress app bundle - run: 'ditto -c -k --sequesterRsrc --keepParent "build/Release/BeezyLight.xcarchive/Products/Applications/BeezyLight.app" "build/Release/BeezyLight.zip"' + - name: Run xcode-build Script + run: './Scripts/xcode-build build "$XCARCHIVE_PATH"' - - name: Upload app artifact + - name: Upload Archive to Artifacts uses: actions/upload-artifact@v3 with: - name: app - path: build/Release/BeezyLight.zip - - - name: Create Release - if: ${{ github.ref_type == 'tag' }} - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref_name }} - release_name: ${{ github.ref_name }} - draft: true - prerelease: false - - - name: Upload Release Asset - if: ${{ github.ref_type == 'tag' }} - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./build/Release/BeezyLight.zip - asset_name: BeezyLight.zip - asset_content_type: application/zip - - - name: Publish Release - if: ${{ github.ref_type == 'tag' }} - run: "hub release edit --draft=false -m '' \"${GITHUB_REF_NAME}\"" - env: - GITHUB_REPOSITORY: ${{ github.repository }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + name: BeezyLight.xcarchive + path: ${{ env.XCARCHIVE_PATH }} + if-no-files-found: error - \ No newline at end of file + - name: Check Git Status + run: git status --porcelain diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..10025ee --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,131 @@ +name: Release + +on: + create: { tags: ['v[0-9]+.[0-9]+.[0-9]+'] } + +jobs: + release: + name: Xcode Build (Release) + runs-on: macos-12 + steps: + - name: Select Xcode Version + run: sudo xcrun xcode-select -s /Applications/Xcode_13.4.app + + - name: Set Environment Variables + run: | + APP_NAME="BeezyLight" + + echo "XCARCHIVE_PATH=${PWD}/${APP_NAME}.xcarchive" >> $GITHUB_ENV + echo "APP_PATH=${PWD}/${APP_NAME}.xcarchive/Products/Applications/${APP_NAME}.app" >> $GITHUB_ENV + echo "ZIP_PATH=${RUNNER_TEMP}/${APP_NAME}.zip" >> $GITHUB_ENV + + echo "BUILD_CERTIFICATE_PATH=${RUNNER_TEMP}/build_certificate.p12" >> $GITHUB_ENV + echo "NOTARIZATION_KEY_PATH=${RUNNER_TEMP}/notarization_key.p8" >> $GITHUB_ENV + echo "KEYCHAIN_PATH=${RUNNER_TEMP}/app-signing.keychain-db" >> $GITHUB_ENV + + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Generate Release Config + run: './Scripts/generate-release-config "${{ secrets.PRODUCT_BUNDLE_IDENTIFIER }}" "${GITHUB_REF_NAME#v}" "$GITHUB_RUN_NUMBER" "${{ secrets.XCODE_DEVELOPMENT_TEAM }}"' + + - name: Install Developer ID Certificate + run: | + # import build certificate from secrets + echo -n "${{ secrets.BUILD_CERTIFICATE_BASE64 }}" | base64 --decode --output "$BUILD_CERTIFICATE_PATH" + + # create temporary keychain + security create-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" "$KEYCHAIN_PATH" + security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" + security unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" "$KEYCHAIN_PATH" + + # import certificate to keychain + security import "$BUILD_CERTIFICATE_PATH" -P "${{ secrets.P12_PASSWORD }}" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH" + security list-keychain -d user -s "$KEYCHAIN_PATH" + + rm "$BUILD_CERTIFICATE_PATH" + + - name: Run xcode-build Script + run: './Scripts/xcode-build release "$XCARCHIVE_PATH"' + + - name: Notarize App + run: | + # Save Notarization Credentials to Keychain + echo -n "${{ secrets.NOTARIZATION_KEY_BASE64 }}" | base64 --decode --output "$NOTARIZATION_KEY_PATH" + xcrun notarytool store-credentials "AppNotarization" \ + -k "$NOTARIZATION_KEY_PATH" \ + -d "${{ secrets.NOTARIZATION_KEY_ID }}" \ + -i "${{ secrets.NOTARIZATION_KEY_ISSUER }}" \ + --keychain "$KEYCHAIN_PATH" + + # create temporary .zip for notarization purposes + ditto -c -k --sequesterRsrc --keepParent "$APP_PATH" "$ZIP_PATH" + + # notarize the app and log the result to stdout + SUBMISSION_ID="$(xcrun notarytool submit "$ZIP_PATH" --keychain-profile "AppNotarization" | awk '$1 ~ /^id:$/ { id=$2 } END { print id }')" + xcrun notarytool wait "$SUBMISSION_ID" --keychain-profile "AppNotarization" + xcrun notarytool log "$SUBMISSION_ID" --keychain-profile "AppNotarization" + + # staple .app bundle with the notarization ticket + xcrun stapler staple -vvv "$APP_PATH" + + rm "$ZIP_PATH" "$NOTARIZATION_KEY_PATH" + + - name: Validate .app bundle + run: | + xcrun stapler validate -vvv "$APP_PATH" + codesign --verify --deep --strict --verbose=1 "$APP_PATH" + spctl --assess --verbose --type open --type exec "$APP_PATH" + + - name: Compress .app bundle + run: 'ditto -c -k --sequesterRsrc --keepParent "$APP_PATH" "$ZIP_PATH"' + + - name: Upload App to Artifacts + uses: actions/upload-artifact@v3 + with: + name: BeezyLight + path: ${{ env.ZIP_PATH }} + if-no-files-found: error + + - name: Upload Archive to Artifacts + uses: actions/upload-artifact@v3 + with: + name: BeezyLight.xcarchive + path: ${{ env.XCARCHIVE_PATH }} + if-no-files-found: error + + - name: Check Git Status + run: git status --porcelain + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref_name }} + release_name: ${{ github.ref_name }} + draft: true + prerelease: false + + - name: Upload Release Asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ${{ env.ZIP_PATH }} + asset_name: BeezyLight.zip + asset_content_type: application/zip + + - name: Publish Release + run: "hub release edit --draft=false -m '' \"${GITHUB_REF_NAME}\"" + env: + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Delete keychain + if: ${{ always() }} + run: | + security delete-keychain "$KEYCHAIN_PATH" + rm -f "$BUILD_CERTIFICATE_PATH" "$NOTARIZATION_KEY_PATH" "$KEYCHAIN_PATH" diff --git a/.gitignore b/.gitignore index 6aecd27..5bd9d5f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ xcuserdata/ -build/ +*.xcarchive +*.zip +Release.xcconfig diff --git a/BeezyLight.xcodeproj/project.pbxproj b/BeezyLight.xcodeproj/project.pbxproj index 4697d28..6b920bb 100644 --- a/BeezyLight.xcodeproj/project.pbxproj +++ b/BeezyLight.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 961F0D2F286F9F8400D35EB3 /* Default.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Default.xcconfig; sourceTree = ""; }; 962377FD27F4ECD8002D718F /* StatusItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusItem.swift; sourceTree = ""; }; 96A53DDC27C572B9002DA809 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 96B8901127F4E78D00C39B4A /* AboutWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutWindow.swift; sourceTree = ""; }; @@ -70,6 +71,7 @@ 96C2A1EA27C5632E00768B18 /* BeezyLight.entitlements */, 96A53DDC27C572B9002DA809 /* Info.plist */, 96DC603427F0F7D000A5FE00 /* Main.storyboard */, + 961F0D2F286F9F8400D35EB3 /* Default.xcconfig */, ); path = BeezyLight; sourceTree = ""; @@ -278,58 +280,53 @@ }; 96C2A1EE27C5632E00768B18 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 961F0D2F286F9F8400D35EB3 /* Default.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = BeezyLight/BeezyLight.entitlements; - CODE_SIGN_IDENTITY = "-"; - CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 0; + ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = BeezyLight/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2020 Leonardo Dino\nAll rights reserved."; INFOPLIST_KEY_NSMainStoryboardFile = Main; INFOPLIST_KEY_NSPrincipalClass = NSApplication; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 0.0.0; - PRODUCT_BUNDLE_IDENTIFIER = app.BeezyLight; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 96C2A1EF27C5632E00768B18 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 961F0D2F286F9F8400D35EB3 /* Default.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = BeezyLight/BeezyLight.entitlements; - CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 0; + ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = BeezyLight/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2020 Leonardo Dino\nAll rights reserved."; INFOPLIST_KEY_NSMainStoryboardFile = Main; INFOPLIST_KEY_NSPrincipalClass = NSApplication; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 0.0.0; - PRODUCT_BUNDLE_IDENTIFIER = app.BeezyLight; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; diff --git a/BeezyLight/Default.xcconfig b/BeezyLight/Default.xcconfig new file mode 100644 index 0000000..7717b96 --- /dev/null +++ b/BeezyLight/Default.xcconfig @@ -0,0 +1,9 @@ +PRODUCT_BUNDLE_IDENTIFIER = open-source.BeezyLight + +CURRENT_PROJECT_VERSION = 0 +MARKETING_VERSION = 0.0.0 +VERSIONING_SYSTEM = apple-generic + +CODE_SIGN_IDENTITY = - +CODE_SIGN_STYLE = Automatic +DEVELOPMENT_TEAM = diff --git a/BeezyLight/Info.plist b/BeezyLight/Info.plist index f743c0e..6f7ae58 100644 --- a/BeezyLight/Info.plist +++ b/BeezyLight/Info.plist @@ -2,11 +2,6 @@ - CFBundleShortVersionString - 0.0.0 - NSHumanReadableCopyright - Copyright © 2020 Leonardo Dino -All rights reserved. LSUIElement diff --git a/Scripts/generate-release-config b/Scripts/generate-release-config new file mode 100755 index 0000000..95545d1 --- /dev/null +++ b/Scripts/generate-release-config @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +RELEASE_CONFIG_PATH="${PWD}/BeezyLight/Release.xcconfig" + +USAGE="Usage: + $(basename "$0") + +Where: + bundle-id: App Bundle ID + marketing-version: Semantic Versioning + build-version: Build Number (Integer) + development-team: Apple Code-Signing Team ID" + +NUMBER_OF_EXPECTED_PARAMETERS=4 +PRODUCT_BUNDLE_IDENTIFIER="$1" +MARKETING_VERSION="$2" +CURRENT_PROJECT_VERSION="$3" +DEVELOPMENT_TEAM="$4" + +if [ "$#" -ne "$NUMBER_OF_EXPECTED_PARAMETERS" ]; then + echo "Error: Invalid number of parameters + Expected: ${NUMBER_OF_EXPECTED_PARAMETERS} parameters + Received: $# parameters + +${USAGE}" >&2 + exit 1 +fi + +if [[ ! "$MARKETING_VERSION" =~ ^[0-9]+.[0-9]+.[0-9]+$ ]]; then + echo "Error: Marketing Version argument must be a SemVer string + Received: \"${MARKETING_VERSION}\" + +${USAGE}" >&2 + exit 1 +fi + +if [[ ! "$CURRENT_PROJECT_VERSION" =~ ^[0-9]+$ ]]; then + echo "Error: Build Number argument must be an Integer + Received: \"${CURRENT_PROJECT_VERSION}\" + +${USAGE}" >&2 + exit 1 +fi + +if [[ ! "$DEVELOPMENT_TEAM" =~ ^[0-9A-Z]{8,12}$ ]]; then + echo "Error: Development Team argument must be valid + Received: \"${DEVELOPMENT_TEAM}\" + +${USAGE}" >&2 + exit 1 +fi + +echo -n "PRODUCT_BUNDLE_IDENTIFIER = ${PRODUCT_BUNDLE_IDENTIFIER} + +CURRENT_PROJECT_VERSION = ${CURRENT_PROJECT_VERSION} +MARKETING_VERSION = ${MARKETING_VERSION} +VERSIONING_SYSTEM = apple-generic + +CODE_SIGN_IDENTITY = Developer ID Application +CODE_SIGN_STYLE = Manual +DEVELOPMENT_TEAM = ${DEVELOPMENT_TEAM} +" | tee "$RELEASE_CONFIG_PATH" diff --git a/Scripts/xcode-build b/Scripts/xcode-build new file mode 100755 index 0000000..9baebd2 --- /dev/null +++ b/Scripts/xcode-build @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +RELEASE_CONFIG_PATH="${PWD}/BeezyLight/Release.xcconfig" +SCHEME="BeezyLight" +WORKSPACE="./${SCHEME}.xcodeproj/project.xcworkspace" + +USAGE="Usage:\n$(basename "$0") [release|build] [archive-path]" +BUILD_TYPE="${1:-build}" +ARCHIVE_PATH="${2:-"${PWD}/${SCHEME}.xcarchive"}" + +if [ "$BUILD_TYPE" == 'release' ]; then + if [ ! -f "$RELEASE_CONFIG_PATH" ]; then + echo "Error: Missing release config file" >&2 + exit 1 + fi + xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -configuration Release -destination "generic/platform=macOS" -archivePath "$ARCHIVE_PATH" -xcconfig "$RELEASE_CONFIG_PATH" archive +elif [ "$BUILD_TYPE" == 'build' ]; then + xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -configuration Release -destination "generic/platform=macOS" -archivePath "$ARCHIVE_PATH" archive +else + echo "Error: Invalid Build Type argument. + Received: \"${BUILD_TYPE}\" + Expected: one of [\"build\" | \"release\"] + +${USAGE}" >&2 + exit 1 +fi