Skip to content

Commit c8347d0

Browse files
refactored notarization
1 parent 9b1b516 commit c8347d0

9 files changed

Lines changed: 366 additions & 26 deletions

File tree

.github/workflows/validate.yml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ jobs:
1919
env:
2020
VERSION: ''
2121
TEMPLATE_PATH: ''
22+
EXPORT_OPTION: ''
2223
UNITY_PROJECT_PATH: ''
2324
runs-on: ${{ matrix.os }}
2425
strategy:
@@ -69,6 +70,15 @@ jobs:
6970
exit 1
7071
}
7172
echo "VERSION=$version" >> $env:GITHUB_ENV
73+
74+
# if the unity-version is 6000.x then set export option to app-store-connect otherwise set it to development
75+
if ('${{ matrix.unity-version }}' -eq '6000.x') {
76+
echo "EXPORT_OPTION=app-store-connect" >> $env:GITHUB_ENV
77+
} elseif ('${{ matrix.unity-version }}' -eq '2022.3.x') {
78+
echo "EXPORT_OPTION=development" >> $env:GITHUB_ENV
79+
} else {
80+
echo "EXPORT_OPTION=steam" >> $env:GITHUB_ENV
81+
}
7282
shell: pwsh
7383
- uses: buildalon/activate-unity-license@v1
7484
with:
@@ -103,8 +113,8 @@ jobs:
103113
app-store-connect-key-id: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
104114
app-store-connect-issuer-id: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
105115
team-id: ${{ secrets.APPLE_TEAM_ID }}
106-
export-option: app-store
107-
upload: ${{ matrix.unity-version == '6000.x' }}
116+
export-option: ${{ env.EXPORT_OPTION }}
117+
notarize: ${{ matrix.unity-version != '6000.x' }}
108118
- name: print outputs
109119
if: always()
110120
run: |

dist/index.js

Lines changed: 117 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57691,9 +57691,9 @@ async function updateBetaBuildLocalization(betaBuildLocalization, whatsNew) {
5769157691
async function pollForValidBuild(project, maxRetries = 60, interval = 30) {
5769257692
var _a, _b, _c;
5769357693
(0, utilities_1.log)(`Polling build validation...`);
57694-
await new Promise(resolve => setTimeout(resolve, interval * 1000));
5769557694
let retries = 0;
5769657695
while (++retries < maxRetries) {
57696+
await new Promise(resolve => setTimeout(resolve, interval * 1000));
5769757697
core.info(`Polling for build... Attempt ${retries}/${maxRetries}`);
5769857698
let { preReleaseVersion, build } = await getLastPreReleaseVersionAndBuild(project);
5769957699
if (preReleaseVersion) {
@@ -57728,7 +57728,6 @@ async function pollForValidBuild(project, maxRetries = 60, interval = 30) {
5772857728
else {
5772957729
core.info(`Waiting for pre-release build ${project.versionString}...`);
5773057730
}
57731-
await new Promise(resolve => setTimeout(resolve, interval * 1000));
5773257731
}
5773357732
throw new Error('Timed out waiting for valid build!');
5773457733
}
@@ -58403,21 +58402,27 @@ async function ExportXcodeArchive(projectRef) {
5840358402
if (projectRef.platform === 'macOS') {
5840458403
if (!projectRef.isAppStoreUpload()) {
5840558404
const notarizeInput = core.getInput('notarize') || 'true';
58405+
projectRef.executablePath = await getFirstPathWithGlob(`${projectRef.exportPath}/**/*.app`);
5840658406
const notarize = notarizeInput === 'true';
5840758407
core.debug(`Notarize? ${notarize}`);
5840858408
if (notarize) {
58409-
projectRef.executablePath = await createMacOSInstallerPkg(projectRef);
58410-
}
58411-
else {
58412-
projectRef.executablePath = await getFileAtGlobPath(`${projectRef.exportPath}/**/*.app`);
58409+
await signMacOSAppBundle(projectRef);
58410+
if (projectRef.exportOption !== 'steam') {
58411+
projectRef.executablePath = await createMacOSInstallerPkg(projectRef);
58412+
}
58413+
else {
58414+
const zipPath = path.join(projectRef.exportPath, projectRef.executablePath.replace('.app', '.zip'));
58415+
await (0, exec_1.exec)('ditto', ['-c', '-k', '--sequesterRsrc', '--keepParent', projectRef.executablePath, zipPath]);
58416+
await notarizeArchive(projectRef, zipPath, projectRef.executablePath);
58417+
}
5841358418
}
5841458419
}
5841558420
else {
58416-
projectRef.executablePath = await getFileAtGlobPath(`${projectRef.exportPath}/**/*.pkg`);
58421+
projectRef.executablePath = await getFirstPathWithGlob(`${projectRef.exportPath}/**/*.pkg`);
5841758422
}
5841858423
}
5841958424
else {
58420-
projectRef.executablePath = await getFileAtGlobPath(`${projectRef.exportPath}/**/*.ipa`);
58425+
projectRef.executablePath = await getFirstPathWithGlob(`${projectRef.exportPath}/**/*.ipa`);
5842158426
}
5842258427
try {
5842358428
await fs.promises.access(projectRef.executablePath, fs.constants.R_OK);
@@ -58429,34 +58434,134 @@ async function ExportXcodeArchive(projectRef) {
5842958434
core.setOutput('executable', projectRef.executablePath);
5843058435
return projectRef;
5843158436
}
58432-
async function getFileAtGlobPath(globPattern) {
58437+
async function getFirstPathWithGlob(globPattern) {
5843358438
const globber = await glob.create(globPattern);
5843458439
const files = await globber.glob();
5843558440
if (files.length === 0) {
5843658441
throw new Error(`No file found at: ${globPattern}`);
5843758442
}
5843858443
return files[0];
5843958444
}
58445+
async function signMacOSAppBundle(projectRef) {
58446+
const appPath = await getFirstPathWithGlob(`${projectRef.exportPath}/**/*.app`);
58447+
await fs.promises.access(appPath, fs.constants.R_OK);
58448+
const stat = await fs.promises.stat(appPath);
58449+
if (!stat.isDirectory()) {
58450+
throw new Error(`Not a valid app bundle: ${appPath}`);
58451+
}
58452+
const signAppBundlePath = __nccwpck_require__.ab + "sign-app-bundle.sh";
58453+
core.info(`Signing app bundle: ${appPath}`);
58454+
let codesignOutput = '';
58455+
const codesignExitCode = await (0, exec_1.exec)(__nccwpck_require__.ab + "sign-app-bundle.sh", [appPath, projectRef.entitlementsPath], {
58456+
listeners: {
58457+
stdout: (data) => {
58458+
codesignOutput += data.toString();
58459+
}
58460+
},
58461+
ignoreReturnCode: true
58462+
});
58463+
if (codesignExitCode !== 0) {
58464+
(0, utilities_1.log)(codesignOutput, 'error');
58465+
throw new Error(`Failed to code sign the app!`);
58466+
}
58467+
core.info(codesignOutput);
58468+
}
5844058469
async function createMacOSInstallerPkg(projectRef) {
5844158470
core.info('Creating macOS installer pkg...');
5844258471
let output = '';
5844358472
const pkgPath = `${projectRef.exportPath}/${projectRef.projectName}.pkg`;
58444-
const appPath = await getFileAtGlobPath(`${projectRef.exportPath}/**/*.app`);
58445-
await (0, exec_1.exec)('productbuild', ['--component', appPath, '/Applications', pkgPath], {
58473+
const appPath = await getFirstPathWithGlob(`${projectRef.exportPath}/**/*.app`);
58474+
const productBuildExitCode = await (0, exec_1.exec)('xcrun', ['productbuild', '--component', appPath, '/Applications', pkgPath], {
5844658475
listeners: {
5844758476
stdout: (data) => {
5844858477
output += data.toString();
5844958478
}
58450-
}
58479+
},
58480+
ignoreReturnCode: true
5845158481
});
58482+
if (productBuildExitCode !== 0) {
58483+
(0, utilities_1.log)(output, 'error');
58484+
throw new Error(`Failed to create the pkg!`);
58485+
}
5845258486
try {
5845358487
await fs.promises.access(pkgPath, fs.constants.R_OK);
5845458488
}
5845558489
catch (error) {
5845658490
throw new Error(`Failed to create the pkg at: ${pkgPath}!`);
5845758491
}
58492+
const signPkgPath = __nccwpck_require__.ab + "sign-app-pkg.sh";
58493+
core.info(`Signing pkg: ${pkgPath}`);
58494+
let codesignOutput = '';
58495+
const codesignExitCode = await (0, exec_1.exec)(__nccwpck_require__.ab + "sign-app-pkg.sh", [pkgPath], {
58496+
listeners: {
58497+
stdout: (data) => {
58498+
codesignOutput += data.toString();
58499+
}
58500+
},
58501+
ignoreReturnCode: true
58502+
});
58503+
if (codesignExitCode !== 0) {
58504+
(0, utilities_1.log)(codesignOutput, 'error');
58505+
throw new Error(`Failed to code sign the pkg!`);
58506+
}
5845858507
return pkgPath;
5845958508
}
58509+
async function notarizeArchive(projectRef, archivePath, staplePath) {
58510+
const notarizeArgs = [
58511+
'notarytool',
58512+
'submit',
58513+
'--key', projectRef.credential.appStoreConnectKeyPath,
58514+
'--key-id', projectRef.credential.appStoreConnectKeyId,
58515+
'--issuer', projectRef.credential.appStoreConnectIssuerId,
58516+
'--team-id', projectRef.credential.teamId,
58517+
'--wait',
58518+
'--no-progress',
58519+
'--output-format', 'json',
58520+
];
58521+
if (core.isDebug()) {
58522+
notarizeArgs.push('--verbose');
58523+
}
58524+
let notarizeOutput = '';
58525+
const notarizeExitCode = await (0, exec_1.exec)(xcrun, [...notarizeArgs, archivePath], {
58526+
listeners: {
58527+
stdout: (data) => {
58528+
notarizeOutput += data.toString();
58529+
}
58530+
},
58531+
ignoreReturnCode: true
58532+
});
58533+
if (notarizeExitCode !== 0) {
58534+
(0, utilities_1.log)(notarizeOutput, 'error');
58535+
throw new Error(`Failed to notarize the app!`);
58536+
}
58537+
core.debug(notarizeOutput);
58538+
const notaryResult = JSON.parse(notarizeOutput);
58539+
if (notaryResult.status !== 'Accepted') {
58540+
throw new Error(`Notarization failed! Status: ${notaryResult.status} | ${notaryResult.message}`);
58541+
}
58542+
const stapleArgs = [
58543+
'stapler',
58544+
'staple',
58545+
staplePath,
58546+
];
58547+
if (core.isDebug()) {
58548+
stapleArgs.push('--verbose');
58549+
}
58550+
let stapleOutput = '';
58551+
const stapleExitCode = await (0, exec_1.exec)(xcrun, stapleArgs, {
58552+
listeners: {
58553+
stdout: (data) => {
58554+
stapleOutput += data.toString();
58555+
}
58556+
},
58557+
ignoreReturnCode: true
58558+
});
58559+
if (stapleExitCode !== 0) {
58560+
(0, utilities_1.log)(stapleOutput, 'error');
58561+
throw new Error(`Failed to staple the notarization ticket!`);
58562+
}
58563+
core.info(stapleOutput);
58564+
}
5846058565
async function getExportOptions(projectRef) {
5846158566
const exportOptionPlistInput = core.getInput('export-option-plist');
5846258567
let exportOptionsPath = undefined;

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/sign-app-bundle.sh

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/bin/bash
2+
# This script is used to sign macOS app bundles with a specified entitlements file.
3+
# Usage: ./signing.sh <path_to_app_bundle> <path_to_entitlements_path>
4+
# Example: ./signing.sh /path/to/MyApp.app /path/to/entitlements.plist
5+
6+
APP_BUNDLE_PATH="$1"
7+
ENTITLEMENTS_PATH="$2"
8+
9+
# remove any metadata from the app bundle
10+
xattr -cr "$APP_BUNDLE_PATH"
11+
12+
# get the signing identity that matches Developer ID Application
13+
SIGNING_IDENTITY=$(security find-identity -p codesigning -v | grep "Developer ID Application" | awk -F'"' '{print $2}' | head -n 1)
14+
if [ -z "$SIGNING_IDENTITY" ]; then
15+
echo "No 'Developer ID Application' signing identity found!"
16+
exit 1
17+
fi
18+
19+
# sign *.bundles and delete any existing *.meta files from them
20+
find "$APP_BUNDLE_PATH" -name "*.bundle" -exec find {} -name '*.meta' -delete \; -exec codesign --deep --force --verify --verbose --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \;
21+
22+
# sign any *.dylibs
23+
find "$APP_BUNDLE_PATH" -name "*.dylib" -exec codesign --force --verify --verbose --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \;
24+
25+
#sign the app bundle
26+
codesign --deep --force --verify --verbose --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" "$APP_BUNDLE_PATH"
27+
28+
# verify the app bundle
29+
if ! codesign --verify --verbose=2 "$APP_BUNDLE_PATH"; then
30+
exit 1
31+
fi

dist/sign-app-pkg.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/bin/bash
2+
# This script is meant to sign macOS .pkg files.
3+
# Usage: ./sign-app-pkg.sh <path_to_pkg>
4+
# Example: ./sign-app-pkg.sh /path/to/MyApp.pkg
5+
6+
PKG_PATH="$1"
7+
8+
# find the Developer ID Installer signing identity
9+
SIGNING_IDENTITY=$(security find-identity -v | grep "Developer ID Installer" | awk -F'"' '{print $2}' | head -n 1)
10+
if [ -z "$SIGNING_IDENTITY" ]; then
11+
echo "No 'Developer ID Installer' signing identity found!"
12+
exit 1
13+
fi
14+
15+
productsign --sign "$SIGNING_IDENTITY" "$PKG_PATH" "${PKG_PATH%.pkg}-signed.pkg"
16+
17+
# verify the signed package
18+
if ! pkgutil --check-signature "${PKG_PATH%.pkg}-signed.pkg"; then
19+
exit 1
20+
fi
21+
22+
# replace the original package with the signed one
23+
rm "$PKG_PATH"
24+
mv "${PKG_PATH%.pkg}-signed.pkg" "$PKG_PATH"
25+
echo "Package signed successfully!"

src/AppStoreConnectClient.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,9 +246,9 @@ async function updateBetaBuildLocalization(betaBuildLocalization: BetaBuildLocal
246246

247247
async function pollForValidBuild(project: XcodeProject, maxRetries: number = 60, interval: number = 30): Promise<Build> {
248248
log(`Polling build validation...`);
249-
await new Promise(resolve => setTimeout(resolve, interval * 1000));
250249
let retries = 0;
251250
while (++retries < maxRetries) {
251+
await new Promise(resolve => setTimeout(resolve, interval * 1000));
252252
core.info(`Polling for build... Attempt ${retries}/${maxRetries}`);
253253
let { preReleaseVersion, build } = await getLastPreReleaseVersionAndBuild(project);
254254
if (preReleaseVersion) {
@@ -280,7 +280,6 @@ async function pollForValidBuild(project: XcodeProject, maxRetries: number = 60,
280280
} else {
281281
core.info(`Waiting for pre-release build ${project.versionString}...`);
282282
}
283-
await new Promise(resolve => setTimeout(resolve, interval * 1000));
284283
}
285284
throw new Error('Timed out waiting for valid build!');
286285
}

src/sign-app-bundle.sh

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/bin/bash
2+
# This script is used to sign macOS app bundles with a specified entitlements file.
3+
# Usage: ./signing.sh <path_to_app_bundle> <path_to_entitlements_path>
4+
# Example: ./signing.sh /path/to/MyApp.app /path/to/entitlements.plist
5+
6+
APP_BUNDLE_PATH="$1"
7+
ENTITLEMENTS_PATH="$2"
8+
9+
# remove any metadata from the app bundle
10+
xattr -cr "$APP_BUNDLE_PATH"
11+
12+
# get the signing identity that matches Developer ID Application
13+
SIGNING_IDENTITY=$(security find-identity -p codesigning -v | grep "Developer ID Application" | awk -F'"' '{print $2}' | head -n 1)
14+
if [ -z "$SIGNING_IDENTITY" ]; then
15+
echo "No 'Developer ID Application' signing identity found!"
16+
exit 1
17+
fi
18+
19+
# sign *.bundles and delete any existing *.meta files from them
20+
find "$APP_BUNDLE_PATH" -name "*.bundle" -exec find {} -name '*.meta' -delete \; -exec codesign --deep --force --verify --verbose --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \;
21+
22+
# sign any *.dylibs
23+
find "$APP_BUNDLE_PATH" -name "*.dylib" -exec codesign --force --verify --verbose --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" {} \;
24+
25+
#sign the app bundle
26+
codesign --deep --force --verify --verbose --timestamp --options runtime --entitlements "$ENTITLEMENTS_PATH" --sign "$SIGNING_IDENTITY" "$APP_BUNDLE_PATH"
27+
28+
# verify the app bundle
29+
if ! codesign --verify --verbose=2 "$APP_BUNDLE_PATH"; then
30+
exit 1
31+
fi

src/sign-app-pkg.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/bin/bash
2+
# This script is meant to sign macOS .pkg files.
3+
# Usage: ./sign-app-pkg.sh <path_to_pkg>
4+
# Example: ./sign-app-pkg.sh /path/to/MyApp.pkg
5+
6+
PKG_PATH="$1"
7+
8+
# find the Developer ID Installer signing identity
9+
SIGNING_IDENTITY=$(security find-identity -v | grep "Developer ID Installer" | awk -F'"' '{print $2}' | head -n 1)
10+
if [ -z "$SIGNING_IDENTITY" ]; then
11+
echo "No 'Developer ID Installer' signing identity found!"
12+
exit 1
13+
fi
14+
15+
productsign --sign "$SIGNING_IDENTITY" "$PKG_PATH" "${PKG_PATH%.pkg}-signed.pkg"
16+
17+
# verify the signed package
18+
if ! pkgutil --check-signature "${PKG_PATH%.pkg}-signed.pkg"; then
19+
exit 1
20+
fi
21+
22+
# replace the original package with the signed one
23+
rm "$PKG_PATH"
24+
mv "${PKG_PATH%.pkg}-signed.pkg" "$PKG_PATH"
25+
echo "Package signed successfully!"

0 commit comments

Comments
 (0)