Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions client/capacitor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ npm run action client/capacitor/build ios

This runs the full build: the web bundle, `cap sync ios` (tun2socks + native sync), and an **unsigned** `xcodebuild` of `ios/App/App.xcodeproj` for a generic iOS device. It is what CI runs. Because it disables code signing, the resulting app cannot be installed on a device; to run the app, use the steps below instead.

### Build the release

Releases are built and published by the scripts in [outline-release](https://github.com/OutlineFoundation/outline-release), which supply the Sentry DSN and the development team and call this action:
Comment thread
greptile-apps[bot] marked this conversation as resolved.

```sh
npm run action client/capacitor/build ios -- --buildMode=release --versionName=<version>
```

It reads `SENTRY_DSN` and `DEVELOPMENT_TEAM` from the environment, and needs that team's signing identity set up in Xcode. It runs `xcodebuild archive` with the given version and the build number (hours since the Unix epoch), and leaves the signed archive at `output/client/ios/Outline.xcarchive`.

### Steps to build and start the app

1. **Build the web bundle** (`www/`), from the **repository root**:
Expand Down
73 changes: 60 additions & 13 deletions client/capacitor/build.action.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import os from 'os';
import path from 'path';
import url from 'url';

import {getRootDir} from '@outline/infrastructure/build/get_root_dir.mjs';
import {runAction} from '@outline/infrastructure/build/run_action.mjs';
import {spawnStream} from '@outline/infrastructure/build/spawn_stream.mjs';

Expand All @@ -43,14 +44,17 @@ export async function main(...parameters) {
);
}

// TODO: Support an iOS release build once we're ready to migrate to Capacitor.
if (buildMode === 'release' && platform !== 'android') {
throw new TypeError(
`Capacitor ${platform} build supports only debug mode, got "${buildMode}".`
);
// Check the release signing inputs before the (slow) web and Go builds.
if (platform === 'ios' && buildMode === 'release') {
// The app target has no development team of its own (see
// ios/App/App.xcodeproj), so a signed build needs one from the caller.
if (!process.env.DEVELOPMENT_TEAM) {
throw new ReferenceError(
'DEVELOPMENT_TEAM must be defined in the environment to build an iOS Release!'
);
}
}

// Check the release signing inputs before the (slow) web and Go builds.
if (platform === 'android' && buildMode === 'release') {
if (!process.env.JAVA_HOME) {
throw new ReferenceError(
Expand Down Expand Up @@ -104,6 +108,13 @@ export async function main(...parameters) {
);
case 'ios' + 'debug':
return iosDebug(verbose);
case 'ios' + 'release':
return iosRelease(
process.env.DEVELOPMENT_TEAM,
versionName,
buildNumber,
Comment thread
greptile-apps[bot] marked this conversation as resolved.
verbose
);
}
}

Expand Down Expand Up @@ -196,25 +207,28 @@ async function androidRelease(
}
}

const IOS_XCODE_BUILD_ARGS = [
'-project',
path.resolve(capacitorDir, 'ios', 'App', 'App.xcodeproj'),
'-scheme',
'Outline',
'-destination',
'generic/platform=iOS',
];

async function iosDebug(verbose) {
// `cap build` only produces signed release builds, so invoke xcodebuild
// directly for an unsigned debug build, the same way the Cordova iOS build
// does (see client/src/cordova/build.action.mjs). Signing is disabled so the
// build needs no development team or provisioning profile, which CI does not
// have.
// TODO: Migrate to an archive once we have a production build.
console.warn(
'WARNING: building "ios" in [DEBUG] mode. Do not publish this build!!'
);
await spawnStream(
'xcodebuild',
'clean',
'-project',
path.resolve(capacitorDir, 'ios', 'App', 'App.xcodeproj'),
'-scheme',
'Outline',
'-destination',
'generic/platform=iOS',
...IOS_XCODE_BUILD_ARGS,
'build',
'-configuration',
'Debug',
Expand All @@ -224,6 +238,39 @@ async function iosDebug(verbose) {
);
}

/**
* Archives the signed release app to output/client/ios/Outline.xcarchive,
* replacing any previous archive there.
*/
async function iosRelease(teamId, versionName, buildNumber, verbose) {
const archivePath = path.resolve(
getRootDir(),
'output',
'client',
'ios',
'Outline.xcarchive'
);
await fs.rm(archivePath, {recursive: true, force: true});

await spawnStream(
'xcodebuild',
'clean',
...IOS_XCODE_BUILD_ARGS,
'archive',
'-archivePath',
archivePath,
'-configuration',
'Release',
Comment thread
greptile-apps[bot] marked this conversation as resolved.
'-allowProvisioningUpdates',
...(verbose ? [] : ['-quiet']),
// The Info.plist of the app and of the VpnExtension take their version
// from these build settings.
`MARKETING_VERSION=${versionName}`,
`CURRENT_PROJECT_VERSION=${buildNumber}`,
`DEVELOPMENT_TEAM=${teamId}`
);
}

if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
await main(...process.argv.slice(2));
}
Loading