Skip to content

Commit

Permalink
Add buildScript option (#504)
Browse files Browse the repository at this point in the history
Adds new configuration options to launch config:
- `buildScript` – replacing default build process with any script that
outputs built app's path as last line of standard output (e.g. fetching
app binary from the internet or using built app from local fs). It's an
object with `ios` and `android` string keys.
- `eas` – replacing default build process with fetching app from EAS
build service. It's an object with `profile`, `useBuildType`, and
`buildUUID` keys. `profile` is used to select correct build profile from
`eas.json`, `useBuildType` is either `"latest"` or `"id"` used to select
either latest suitable build or selecting build via build UUID.
`buildUUID` is required when using `useBuildType: "id"`.

You can't specify both `eas` and `buildScript` for one platform.

## Test plan

- `buildScript` – simple `"echo SOME_PATH"` script for both platforms.
- `eas` – tested both platforms, for "latest" and "id" strategies.

## Followups

- Add "matchAppVersion" option to use builds that match current branch
app version for platform.
- If using EAS, notify the user in the UI and provide a link to monitor
build process (or periodically poll API that returns progress if it
exists).
- Consider best location to store build artifacts (right now uses
system's temporary directory).
- Consider allowing drag & drop for built artefacts.
- Consider building a screen to select builds.

Resolves #150.
  • Loading branch information
jakub-gonet authored Oct 3, 2024
1 parent 6e6abf4 commit 175ea20
Show file tree
Hide file tree
Showing 15 changed files with 638 additions and 60 deletions.
59 changes: 59 additions & 0 deletions packages/docs/docs/launch-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,65 @@ Below is an example of how the `launch.json` file could look like with android v
}
```

### Custom build settings

Instead of letting Radon IDE build your app, you can use scripts (`buildScript` option) or [Expo
Application Services (EAS)](https://expo.dev/eas) (`eas` option) to do it.

The requirement for scripts is to output the absolute path to the built app as the
last line of the standard output.

Both `buildScript` and `eas` are objects having `ios` and `android` optional
keys. You can't specify one platform in both custom script and EAS build
options.

`buildScript.ios` and `buildScript.android` are string keys, representing custom
command used to build the app. Example below:
```json
{
"version": "0.2.0",
"configurations": [
{
"type": "radon-ide",
"request": "launch",
"name": "Radon IDE panel",
"buildScript": {
"android": "npm run build:ftp-fetch-android"
}
}
]
}
```

`eas.ios` and `eas.android` are objects taking keys:
- `profile` – required, used for [selecting builds](https://docs.expo.dev/build/eas-json/#development-builds) suitable for running in simulators.
- `buildUUID` – when specified, downloads build using its UUID. It uses latest
build otherwise.

Below is an example that replaces iOS and Android local builds with builds from EAS:
```json
{
"version": "0.2.0",
"configurations": [
{
"type": "radon-ide",
"request": "launch",
"name": "Radon IDE panel",
"eas": {
"ios": {
"profile": "development",
},
"android": {
"profile": "development",
"buildUUID": "40466d0a-96fa-5d9c-80db-d055e78023bd"
}
}
}
]
}
```


### Other settings

Here, we list other attributes that can be configured using launch configuration which doesn't fit in any of the above categories:
Expand Down
54 changes: 53 additions & 1 deletion packages/vscode-extension/.vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,57 @@
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off",
"cmake.configureOnOpen": false,
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"cSpell.words": [
"Aapt",
"avds",
"codicon",
"codicons",
"debugadapter",
"debuggable",
"debugprotocol",
"Deeplink",
"devmenu",
"execa",
"fastboot",
"getprop",
"gradlew",
"initscript",
"keyevent",
"launchservices",
"libexec",
"msgpack",
"ncore",
"openurl",
"outfile",
"playstore",
"Preact",
"prefs",
"RNIDE",
"Runtimes",
"schemeapproval",
"sdcard",
"sdkmanager",
"sharedpreferences",
"simctl",
"siri",
"sourcer",
"swmansion",
"sysdir",
"UDID",
"uimode",
"uuidv4",
"virtualscene",
"xcodebuild",
"xcodeproj",
"xcrun",
"xcscheme",
"xcschemes",
"xcshareddata",
"xcworkspace",
"xlarge",
"xsmall",
"xxlarge",
"xxxlarge"
]
}
3 changes: 2 additions & 1 deletion packages/vscode-extension/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

60 changes: 57 additions & 3 deletions packages/vscode-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
"side-panel",
"secondary-side-panel"
],
"description": "Controlls location of the IDE panel. Due to vscode API limitations, when secondary side panel is selected, you need to manually move the IDE panel to the secondary side panel. Changing this option closes and reopens the IDE."
"description": "Controls location of the IDE panel. Due to vscode API limitations, when secondary side panel is selected, you need to manually move the IDE panel to the secondary side panel. Changing this option closes and reopens the IDE."
},
"RadonIDE.showDeviceFrame": {
"type": "boolean",
Expand Down Expand Up @@ -194,7 +194,7 @@
"properties": {
"appRoot": {
"type": "string",
"description": "Location of the React Native application root folder relative to the workspace. This is used for monorepo type setups when the workspace root is not the root of the React Native project. The IDE extension tries to locate the React Native application root automatically, but in case it failes to do so (i.e. there are multiple applications defined in the workspace), you can use this setting to override the location."
"description": "Location of the React Native application root folder relative to the workspace. This is used for monorepo type setups when the workspace root is not the root of the React Native project. The IDE extension tries to locate the React Native application root automatically, but in case it fails to do so (i.e. there are multiple applications defined in the workspace), you can use this setting to override the location."
},
"isExpo": {
"type": "boolean",
Expand All @@ -208,6 +208,60 @@
"type": "string",
"description": "Location of Metro config relative to the workspace. This is used for using custom configs for e.g. development."
},
"buildScript": {
"type": "object",
"description": "Scripts used to build Android or iOS app or fetch them from known location. Executed as a part of building process. Should print a JSON result from `eas build` command or a filesystem path to the built app as the last line of the standard output.\nIf using EAS, it should be invoked with --json --non-interactive flags and use a profile for development, iOS additionally needs `\"ios.simulator\": true` config option in eas.json",
"properties": {
"ios": {
"type": "string",
"description": "Script used to build iOS app."
},
"android": {
"type": "string",
"description": "Script used to build Android app."
}
}
},
"eas": {
"type": "object",
"description": "Configuration for EAS build service",
"properties": {
"android": {
"type": "object",
"description": "Configuration for EAS build service for Android",
"properties": {
"profile": {
"type": "string",
"description": "Profile used to build the app in EAS. Built app should be in debug version."
},
"buildUUID": {
"type": "string",
"description": "UUID of the EAS build used in `eas build:view` command."
}
},
"required": [
"profile"
]
},
"ios": {
"type": "object",
"description": "Configuration for EAS build service for iOS",
"properties": {
"profile": {
"type": "string",
"description": "Profile used to build the app in EAS. Built app should be in debug version and have '\"ios.simulator\": true' option set."
},
"buildUUID": {
"type": "string",
"description": "UUID of the EAS build used in `eas build:view` command."
}
},
"required": [
"profile"
]
}
}
},
"ios": {
"description": "Provides a way to customize Xcode builds for iOS",
"type": "object",
Expand Down Expand Up @@ -322,7 +376,7 @@
}
},
"preview": {
"description": "Custommize the behavior of device preview",
"description": "Customize the behavior of device preview",
"type": "object",
"properties": {
"waitForAppLaunch": {
Expand Down
46 changes: 41 additions & 5 deletions packages/vscode-extension/src/builders/buildAndroid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { getLaunchConfiguration } from "../utilities/launchConfiguration";
import { EXPO_GO_PACKAGE_NAME, downloadExpoGo, isExpoGoProject } from "./expoGo";
import { DevicePlatform } from "../common/DeviceManager";
import { getReactNativeVersion } from "../utilities/reactNative";
import { runExternalBuild } from "./customBuild";
import { fetchEasBuild } from "./eas";

export type AndroidBuildResult = {
platform: DevicePlatform.Android;
Expand Down Expand Up @@ -76,14 +78,48 @@ export async function buildAndroid(
outputChannel: OutputChannel,
progressListener: (newProgress: number) => void
): Promise<AndroidBuildResult> {
const { buildScript, eas, env, android } = getLaunchConfiguration();

if (buildScript?.android && eas?.android) {
throw new Error(
"Both custom build script and EAS build are configured for Android. Please use only one build method."
);
}

if (buildScript?.android) {
const apkPath = await runExternalBuild(cancelToken, buildScript.android, env);
if (!apkPath) {
throw new Error("Failed to build Android app using custom script.");
}

return {
apkPath,
packageName: await extractPackageName(apkPath, cancelToken),
platform: DevicePlatform.Android,
};
}

if (eas?.android) {
const apkPath = await fetchEasBuild(cancelToken, eas.android, DevicePlatform.Android);
if (!apkPath) {
throw new Error("Failed to build Android app using EAS build.");
}

return {
apkPath,
packageName: await extractPackageName(apkPath, cancelToken),
platform: DevicePlatform.Android,
};
}

if (await isExpoGoProject()) {
const apkPath = await downloadExpoGo(DevicePlatform.Android, cancelToken);
return { apkPath, packageName: EXPO_GO_PACKAGE_NAME, platform: DevicePlatform.Android };
}

const androidSourceDir = getAndroidSourceDir(appRootFolder);
const buildOptions = getLaunchConfiguration();
const productFlavor = buildOptions.android?.productFlavor || "";
const buildType = buildOptions.android?.buildType || "debug";
const productFlavor = android?.productFlavor || "";
const buildType = android?.buildType || "debug";
const gradleArgs = [
"-x",
"lint",
Expand Down Expand Up @@ -114,7 +150,7 @@ export async function buildAndroid(
const buildProcess = cancelToken.adapt(
exec("./gradlew", gradleArgs, {
cwd: androidSourceDir,
env: { ...buildOptions.env, JAVA_HOME, ANDROID_HOME },
env: { ...env, JAVA_HOME, ANDROID_HOME },
buffer: false,
})
);
Expand All @@ -126,7 +162,7 @@ export async function buildAndroid(
});

await buildProcess;
Logger.debug("Android build sucessful");
Logger.debug("Android build successful");
const apkInfo = await getAndroidBuildPaths(appRootFolder, cancelToken, productFlavor, buildType);
return { ...apkInfo, platform: DevicePlatform.Android };
}
36 changes: 35 additions & 1 deletion packages/vscode-extension/src/builders/buildIOS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { getLaunchConfiguration } from "../utilities/launchConfiguration";
import { IOSDeviceInfo, DevicePlatform } from "../common/DeviceManager";
import { EXPO_GO_BUNDLE_ID, downloadExpoGo, isExpoGoProject } from "./expoGo";
import { findXcodeProject, findXcodeScheme, IOSProjectInfo } from "../utilities/xcode";
import { runExternalBuild } from "./customBuild";
import { fetchEasBuild } from "./eas";
import { getXcodebuildArch } from "../utilities/common";

export type IOSBuildResult = {
Expand Down Expand Up @@ -76,7 +78,39 @@ export async function buildIos(
progressListener: (newProgress: number) => void,
installPodsIfNeeded: () => Promise<void>
): Promise<IOSBuildResult> {
const { ios: buildOptions } = getLaunchConfiguration();
const { buildScript, eas, ios: buildOptions, env } = getLaunchConfiguration();

if (buildScript?.ios && eas?.ios) {
throw new Error(
"Both custom build script and EAS build are configured for iOS. Please use only one build method."
);
}

if (buildScript?.ios) {
const appPath = await runExternalBuild(cancelToken, buildScript.ios, env);
if (!appPath) {
throw new Error("Failed to build iOS app using custom script.");
}

return {
appPath,
bundleID: await getBundleID(appPath),
platform: DevicePlatform.IOS,
};
}

if (eas?.ios) {
const appPath = await fetchEasBuild(cancelToken, eas.ios, DevicePlatform.IOS);
if (!appPath) {
throw new Error("Failed to build iOS app using EAS build.");
}

return {
appPath,
bundleID: await getBundleID(appPath),
platform: DevicePlatform.IOS,
};
}

if (await isExpoGoProject()) {
const appPath = await downloadExpoGo(DevicePlatform.IOS, cancelToken);
Expand Down
Loading

0 comments on commit 175ea20

Please sign in to comment.