Compare commits

...
7 changed files with 341 additions and 100 deletions

No files matched your search

+22 -6
View File
@@ -1,4 +1,11 @@
import { Android, Job, Metadata, Platform, Workflow } from '@expo/eas-build-job';
import {
Android,
AndroidWorkerImageWithAliases,
Job,
Metadata,
Platform,
Workflow,
} from '@expo/eas-build-job';
import { AppVersionSource } from '@expo/eas-json';
import chalk from 'chalk';
import nullthrows from 'nullthrows';
@@ -109,11 +116,20 @@ export async function prepareAndroidBuildAsync(
vcsClient: ctx.vcsClient,
});
},
prepareJobAsync: async (
ctx: BuildContext<Platform.ANDROID>,
jobData: JobData<AndroidCredentials>
): Promise<Job> => {
return await prepareJobAsync(ctx, jobData);
prepareJobAsync: async ({
ctx,
jobData,
resolvedImage,
}: {
ctx: BuildContext<Platform.ANDROID>;
jobData: JobData<AndroidCredentials>;
resolvedImage: AndroidWorkerImageWithAliases;
}): Promise<Job> => {
return await prepareJobAsync({
ctx,
jobData,
resolvedImage,
});
},
sendBuildRequestAsync: async (
appId: string,
@@ -1,5 +1,6 @@
import {
Android,
AndroidWorkerImageWithAliases,
ArchiveSource,
BuildMode,
BuildTrigger,
@@ -26,10 +27,15 @@ const cacheDefaults = {
paths: [],
};
export async function prepareJobAsync(
ctx: BuildContext<Platform.ANDROID>,
jobData: JobData
): Promise<Job> {
export async function prepareJobAsync({
ctx,
jobData,
resolvedImage,
}: {
ctx: BuildContext<Platform.ANDROID>;
jobData: JobData;
resolvedImage: AndroidWorkerImageWithAliases;
}): Promise<Job> {
const username = getUsername(ctx.exp, ctx.user);
const buildProfile: BuildProfile<Platform.ANDROID> = ctx.buildProfile;
const projectRootDirectory =
@@ -63,7 +69,7 @@ export async function prepareJobAsync(
projectRootDirectory,
projectArchive: jobData.projectArchive,
builderEnvironment: {
image: buildProfile.image,
image: resolvedImage,
node: buildProfile.node,
pnpm: buildProfile.pnpm,
bun: buildProfile.bun,
+34 -6
View File
@@ -1,4 +1,12 @@
import { ArchiveSource, ArchiveSourceType, Job, Metadata, Platform } from '@expo/eas-build-job';
import {
AndroidWorkerImageWithAliases,
ArchiveSource,
ArchiveSourceType,
IosWorkerImageWithAliases,
Job,
Metadata,
Platform,
} from '@expo/eas-build-job';
import { CredentialsSource } from '@expo/eas-json';
import assert from 'assert';
import chalk from 'chalk';
@@ -25,6 +33,7 @@ import {
import { transformMetadata } from './graphql';
import { LocalBuildMode, runLocalBuildAsync } from './local';
import { collectMetadataAsync } from './metadata';
import { resolveWorkerImage } from './utils/image';
import { printDeprecationWarnings } from './utils/printBuildInfo';
import { makeProjectTarballAsync, reviewAndCommitChangesAsync } from './utils/repository';
import { BuildEvent } from '../analytics/AnalyticsManager';
@@ -72,7 +81,11 @@ interface Builder<TPlatform extends Platform, Credentials, TJob extends Job> {
ctx: BuildContext<TPlatform>
): Promise<CredentialsResult<Credentials> | undefined>;
syncProjectConfigurationAsync(ctx: BuildContext<TPlatform>): Promise<void>;
prepareJobAsync(ctx: BuildContext<TPlatform>, jobData: JobData<Credentials>): Promise<Job>;
prepareJobAsync(prepareJobAsyncParams: {
ctx: BuildContext<TPlatform>;
jobData: JobData<Credentials>;
resolvedImage: AndroidWorkerImageWithAliases | IosWorkerImageWithAliases;
}): Promise<Job>;
sendBuildRequestAsync(
appId: string,
job: TJob,
@@ -131,6 +144,18 @@ export async function prepareBuildRequestForPlatformAsync<
);
}
const metadata = await collectMetadataAsync(ctx);
const resolvedImage = resolveWorkerImage({ ctx, metadata });
if (resolvedImage === 'default') {
Log.warn(
`The "default" value for image field is deprecated. Specify the image explicitly or use SDK tags. ${learnMore(
ctx.platform === Platform.ANDROID
? 'https://docs.expo.dev/build-reference/infrastructure/#android-server-images'
: 'https://docs.expo.dev/build-reference/infrastructure/#ios-server-images'
)}`
);
}
let projectArchive: ArchiveSource | undefined;
if (ctx.localBuildOptions.localBuildMode === LocalBuildMode.LOCAL_BUILD_PLUGIN) {
projectArchive = {
@@ -150,11 +175,14 @@ export async function prepareBuildRequestForPlatformAsync<
}
assert(projectArchive);
const metadata = await collectMetadataAsync(ctx);
const buildParams = resolveBuildParamsInput(ctx, metadata);
const job = await builder.prepareJobAsync(ctx, {
projectArchive,
credentials: credentialsResult?.credentials,
const job = await builder.prepareJobAsync({
ctx,
jobData: {
projectArchive,
credentials: credentialsResult?.credentials,
},
resolvedImage,
});
return async () => {
+24 -8
View File
@@ -1,4 +1,11 @@
import { Ios, Job, Metadata, Platform, Workflow } from '@expo/eas-build-job';
import {
Ios,
IosWorkerImageWithAliases,
Job,
Metadata,
Platform,
Workflow,
} from '@expo/eas-build-job';
import { AppVersionSource } from '@expo/eas-json';
import { ensureIosCredentialsAsync } from './credentials';
@@ -91,13 +98,22 @@ export async function prepareIosBuildAsync(
vcsClient: ctx.vcsClient,
});
},
prepareJobAsync: async (
ctx: BuildContext<Platform.IOS>,
jobData: JobData<IosCredentials>
): Promise<Job> => {
return await prepareJobAsync(ctx, {
...jobData,
buildScheme: ctx.ios.xcodeBuildContext.buildScheme,
prepareJobAsync: async ({
ctx,
jobData,
resolvedImage,
}: {
ctx: BuildContext<Platform.IOS>;
jobData: JobData<IosCredentials>;
resolvedImage: IosWorkerImageWithAliases;
}): Promise<Job> => {
return await prepareJobAsync({
ctx,
jobData: {
...jobData,
buildScheme: ctx.ios.xcodeBuildContext.buildScheme,
},
resolvedImage,
});
},
sendBuildRequestAsync: async (
+11 -5
View File
@@ -3,6 +3,7 @@ import {
BuildMode,
BuildTrigger,
Ios,
IosWorkerImageWithAliases,
Job,
Platform,
sanitizeJob,
@@ -29,10 +30,15 @@ const cacheDefaults = {
paths: [],
};
export async function prepareJobAsync(
ctx: BuildContext<Platform.IOS>,
jobData: JobData
): Promise<Job> {
export async function prepareJobAsync({
ctx,
jobData,
resolvedImage,
}: {
ctx: BuildContext<Platform.IOS>;
jobData: JobData;
resolvedImage: IosWorkerImageWithAliases;
}): Promise<Job> {
const username = getUsername(ctx.exp, ctx.user);
const buildProfile: BuildProfile<Platform.IOS> = ctx.buildProfile;
const projectRootDirectory =
@@ -55,7 +61,7 @@ export async function prepareJobAsync(
projectArchive: jobData.projectArchive,
projectRootDirectory,
builderEnvironment: {
image: buildProfile.image,
image: resolvedImage,
node: buildProfile.node,
pnpm: buildProfile.pnpm,
bun: buildProfile.bun,
+211
View File
@@ -0,0 +1,211 @@
import {
AndroidWorkerImageWithAliases,
AndroidWorkerImageWithoutAliases,
IosWorkerImageWithAliases,
IosWorkerImageWithoutAliases,
Metadata,
Platform,
Workflow,
} from '@expo/eas-build-job';
import semver from 'semver';
import Log, { learnMore } from '../../log';
import { BuildContext } from '../context';
interface ImageMatchRule<T extends Platform> {
image: T extends Platform.ANDROID
? AndroidWorkerImageWithoutAliases
: IosWorkerImageWithoutAliases;
reactNativeSemverRange?: string;
sdkSemverRange?: string;
workflows?: Workflow[];
}
interface ImageMatchArgs {
sdkVersion?: string;
reactNativeVersion?: string;
workflow: Workflow;
}
type ImageMatchResolveArgs<T extends Platform> = ImageMatchArgs & { platform: T };
export function resolveWorkerImage<TPlatform extends Platform>({
ctx,
metadata,
}: {
ctx: BuildContext<TPlatform>;
metadata: Metadata;
}): AndroidWorkerImageWithAliases | IosWorkerImageWithAliases {
if (ctx.platform === Platform.ANDROID) {
return resolveAndroidWorkerImage({
ctx: ctx as BuildContext<Platform.ANDROID>,
metadata,
});
} else {
return resolveIosWorkerImage({ ctx: ctx as BuildContext<Platform.IOS>, metadata });
}
}
function resolveAndroidWorkerImage({
ctx,
metadata,
}: {
ctx: BuildContext<Platform.ANDROID>;
metadata: Metadata;
}): AndroidWorkerImageWithAliases {
return (
ctx.buildProfile.image ??
resolveDefaultWorkerImageRule({
sdkVersion: metadata.sdkVersion,
workflow: ctx.workflow,
reactNativeVersion: metadata.reactNativeVersion,
platform: ctx.platform,
}).image
);
}
function resolveIosWorkerImage({
ctx,
metadata,
}: {
ctx: BuildContext<Platform.IOS>;
metadata: Metadata;
}): IosWorkerImageWithAliases {
return (
ctx.buildProfile.image ??
resolveDefaultWorkerImageRule({
sdkVersion: metadata.sdkVersion,
workflow: ctx.workflow,
reactNativeVersion: metadata.reactNativeVersion,
platform: ctx.platform,
}).image
);
}
function resolveDefaultWorkerImageRule<T extends Platform>({
sdkVersion,
reactNativeVersion,
workflow,
platform,
}: ImageMatchResolveArgs<T>): ImageMatchRule<T> {
Log.log();
Log.log(
'No image was specified. EAS CLI will resolve the image to be used for this build based on the project configuration, React Native version and Expo SDK version used by the project...'
);
logDataUsedForImageResolution({ sdkVersion, reactNativeVersion, workflow });
const resolveArgs: ImageMatchArgs = {
sdkVersion,
reactNativeVersion,
workflow,
};
const rules = (
platform === Platform.IOS ? iosImageMatchRules : androidImageMatchRules
) as ImageMatchRule<T>[];
for (const rule of rules) {
if (doesImageRuleMatch(rule, resolveArgs)) {
Log.log(`✅ Resolved ${rule.image} image as the best match for the given configuration`);
return rule;
}
}
Log.error(
`❌ EAS CLI wasn't able to automatically find worker image for the given configuration. Specify the image manually in your eas.json configuration file to fix this problem. ${learnMore(
platform === Platform.ANDROID
? 'https://docs.expo.dev/eas/json/#image'
: 'https://docs.expo.dev/eas/json/#image-1'
)}`
);
throw new Error('No worker image found for the given configuration');
}
const iosImageMatchRules: ImageMatchRule<Platform.IOS>[] = [
{
image: 'macos-monterey-12.4-xcode-13.4',
sdkSemverRange: '>=45 <47',
workflows: [Workflow.MANAGED],
},
{
image: 'macos-monterey-12.6-xcode-14.1',
sdkSemverRange: '>=47 <48',
},
{
image: 'macos-monterey-12.6-xcode-14.2',
sdkSemverRange: '>=48 <49',
},
{
image: 'macos-ventura-13.4-xcode-14.3.1',
sdkSemverRange: '>=49 <50',
},
{
image: 'macos-ventura-13.6-xcode-15.2',
sdkSemverRange: '>=50',
},
{
image: 'macos-monterey-12.6-xcode-14.2',
reactNativeSemverRange: '>=0.70.0',
},
];
const androidImageMatchRules: ImageMatchRule<Platform.ANDROID>[] = [
{
image: 'ubuntu-20.04-jdk-11-ndk-r19c',
reactNativeSemverRange: '>=0.68.0',
sdkSemverRange: '<46',
},
{
image: 'ubuntu-20.04-jdk-8-ndk-r19c',
reactNativeSemverRange: '<0.68.0',
sdkSemverRange: '<46',
},
{
image: 'ubuntu-20.04-jdk-11-ndk-r21e',
reactNativeSemverRange: '>=0.68.0 <0.73.0',
sdkSemverRange: '>=46 <50',
},
{
image: 'ubuntu-20.04-jdk-8-ndk-r21e',
reactNativeSemverRange: '<0.68.0',
sdkSemverRange: '>=46 <50',
},
{
image: 'ubuntu-22.04-jdk-17-ndk-r21e',
sdkSemverRange: '>=50',
},
{
image: 'ubuntu-22.04-jdk-17-ndk-r21e',
reactNativeSemverRange: '>=0.73.0',
},
];
function doesImageRuleMatch<T extends Platform>(
rule: ImageMatchRule<T>,
{ sdkVersion, reactNativeVersion, workflow }: ImageMatchArgs
): boolean {
if (rule.reactNativeSemverRange) {
if (!reactNativeVersion || !semver.satisfies(reactNativeVersion, rule.reactNativeSemverRange)) {
return false;
}
}
if (rule.sdkSemverRange) {
if (!sdkVersion || !semver.satisfies(sdkVersion, rule.sdkSemverRange)) {
return false;
}
}
if (rule.workflows && !rule.workflows?.includes(workflow)) {
return false;
}
return true;
}
function logDataUsedForImageResolution({
sdkVersion,
reactNativeVersion,
workflow,
}: ImageMatchArgs): void {
Log.log(`\tExpo SDK version: ${sdkVersion ? sdkVersion : 'Expo SDK not detected'}`);
Log.log(
`\tReact Native version: ${
reactNativeVersion ? reactNativeVersion : 'React Native not detected'
}`
);
Log.log(`\tWorkflow: ${workflow}`);
}
+28 -70
View File
@@ -24,19 +24,14 @@
"version": {
"type": "string",
"description": "The compatible versions of EAS CLI with this config",
"examples": [
">=0.54.1"
]
"examples": [">=0.54.1"]
},
"requireCommit": {
"type": "boolean",
"description": "If all changes required to be committed before building or submitting"
},
"appVersionSource": {
"enum": [
"local",
"remote"
],
"enum": ["local", "remote"],
"markdownDescription": "Version policy defines whether version of your app should be based on your local project or values stored on EAS servers (remote).\n\nThis is the configuration required for `eas build:version:set` and works with the `autoIncrement` options per platform.",
"markdownEnumDescriptions": [
"When using local, the `autoIncrement` is based on your local project values.",
@@ -107,10 +102,7 @@
"description": "The name of the build profile that the current one should inherit values from. This value can't be specified per platform."
},
"credentialsSource": {
"enum": [
"remote",
"local"
],
"enum": ["remote", "local"],
"description": "The source of credentials used to sign build artifacts. Learn more: https://docs.expo.dev/app-signing/local-credentials/",
"markdownDescription": "The source of credentials used to sign build artifacts.\n\n- `remote` - if you want to use the credentials managed by EAS.\n- `local` - if you want to provide your own `credentials.json` file. [learn more](https://docs.expo.dev/app-signing/local-credentials/)",
"default": "remote",
@@ -132,10 +124,7 @@
"markdownDescription": "The channel is a name we can give to multiple builds to identify them easily. [Learn more](https://docs.expo.dev/eas-update/how-eas-update-works/)\n\n**This field only applies to the EAS Update service**, if your project still uses Classic Updates then use the [releaseChannel](https://docs.expo.dev/build-reference/eas-json/#releasechannel) field instead."
},
"distribution": {
"enum": [
"internal",
"store"
],
"enum": ["internal", "store"],
"description": "The method of distributing your app. Learn more: https://docs.expo.dev/build/internal-distribution/",
"markdownDescription": "The method of distributing your app.\n\n- `internal` - with this option you'll be able to share your build URLs with anyone, and they will be able to install the builds to their devices straight from the Expo website. When using `internal`, make sure the build produces an APK or IPA file. Otherwise, the shareable URL will be useless. [Learn more](https://docs.expo.dev/build/internal-distribution/)\n- `store` - produces builds for store uploads, your build URLs won't be shareable.",
"markdownEnumDescriptions": [
@@ -214,11 +203,7 @@
"default": "default",
"anyOf": [
{
"enum": [
"default",
"medium",
"large"
]
"enum": ["default", "medium", "large"]
},
{
"type": "string"
@@ -254,9 +239,9 @@
"anyOf": [
{
"enum": [
"default",
"latest",
"stable",
"sdk-50",
"sdk-49",
"ubuntu-22.04-jdk-17-ndk-r21e",
"ubuntu-22.04-jdk-11-ndk-r21e",
"ubuntu-22.04-jdk-8-ndk-r21e",
@@ -266,13 +251,17 @@
"ubuntu-20.04-jdk-8-ndk-r19c"
],
"markdownEnumDescriptions": [
"- React Native `>=0.68.0` - `ubuntu-22.04-jdk-11-ndk-r21e`\n- React Native `<0.68.0` - `ubuntu-20.04-jdk-8-ndk-r19c`",
"`ubuntu-22.04-jdk-17-ndk-r21e`",
"`ubuntu-22.04-jdk-17-ndk-r21e`",
"`ubuntu-22.04-jdk-11-ndk-r21e`"
]
},
{
"type": "string"
"deprecated": true,
"enum": ["default"],
"markdownEnumDescriptions": [
"This image is deprecated, please use one of the more recent images instead."
]
}
]
},
@@ -282,11 +271,7 @@
"default": "default",
"anyOf": [
{
"enum": [
"default",
"medium",
"large"
]
"enum": ["default", "medium", "large"]
},
{
"type": "string"
@@ -298,12 +283,7 @@
"description": "The version of Android NDK."
},
"autoIncrement": {
"enum": [
false,
true,
"versionCode",
"version"
],
"enum": [false, true, "versionCode", "version"],
"description": "Controls how EAS CLI bumps your application build version.",
"markdownDescription": "Controls how EAS CLI bumps your application build version.\n\nAllowed values:\n- `\"version\"` - bumps the patch of `expo.version` (e.g. `1.2.3` -> `1.2.4`).\n- `\"versionCode\"` (or `true`) - bumps `expo.android.versionCode` (e.g. `3` -> `4`).\n- `false` - versions won't be bumped automatically (default)\n\nIn the case of a bare project, it also updates versions in native code. `expo.version` corresponds to `versionName` and `expo.android.versionCode` to `versionCode` in the `build.gradle`. Google Play uses these values to identify the app build, `versionName` is the version visible to users, whereas `versionCode` defines the version number. The combination of those needs to be unique, so you can bump either of them.\n\nThis feature is not intended for use with dynamic configuration (app.config.js). EAS CLI will throw an error if you don't use app.json.",
"default": false,
@@ -315,10 +295,7 @@
]
},
"buildType": {
"enum": [
"app-bundle",
"apk"
],
"enum": ["app-bundle", "apk"],
"description": "Type of the artifact you want to build.",
"markdownDescription": "Type of the artifact you want to build. It controls what Gradle task will be used, can be overridden by `gradleCommand` or `developmentClient: true` option.\n- `app-bundle` - `:app:bundleRelease`\n- `apk` - `:app:assembleRelease`",
"markdownEnumDescriptions": [
@@ -365,22 +342,14 @@
"default": false
},
"enterpriseProvisioning": {
"enum": [
"universal",
"adhoc"
],
"enum": ["universal", "adhoc"],
"markdownDescription": "Provisioning method used for `\"distribution\": \"internal\"` when you have an Apple account with Apple Developer Enterprise Program membership.\n\nYou can choose if you want to use `adhoc` or `universal` provisioning. The latter is recommended as it does not require you to register each individual device. If you don't provide this option and you still authenticate with an enterprise team, you'll be prompted which provisioning method to use.",
"markdownEnumDescriptions": [
"Recommended as it does not require you to register each individual device"
]
},
"autoIncrement": {
"enum": [
false,
true,
"buildNumber",
"version"
],
"enum": [false, true, "buildNumber", "version"],
"description": "Controls how EAS CLI bumps your application build version.",
"markdownDescription": "Controls how EAS CLI bumps your application build version.\n\nAllowed values:\n\n- `\"version\"` - bumps the patch of `expo.version` (e.g. `1.2.3` -> `1.2.4`).\n- `\"buildNumber\"` (or `true`) - bumps the last component of `expo.ios.buildNumber` (e.g. `1.2.3.39` -> `1.2.3.40`).\n- `false` - versions won't be bumped automatically (default)\n\nIn the case of a bare project, it also updates versions in native code. `expo.version` corresponds to `CFBundleShortVersionString` and `expo.ios.buildNumber` to `CFBundleVersion` in the `Info.plist`. The App Store is using those values to identify the app build, `CFBundleShortVersionString` is the version visible to users, whereas `CFBundleVersion` defines the build number. The combination of those needs to be unique, so you can bump either of them.\n\nThis feature is not intended for use with dynamic configuration (app.config.js). EAS CLI will throw an error if you don't use app.json.",
"default": false,
@@ -398,8 +367,9 @@
"anyOf": [
{
"enum": [
"default",
"latest",
"sdk-50",
"sdk-49",
"macos-ventura-13.6-xcode-15.2",
"macos-ventura-13.6-xcode-15.1",
"macos-ventura-13.6-xcode-15.0",
@@ -410,18 +380,21 @@
"macos-monterey-12.6-xcode-14.0"
],
"markdownEnumDescriptions": [
"`macos-ventura-13.4-xcode-14.3.1`",
"`macos-ventura-13.6-xcode-15.2`"
"`macos-ventura-13.6-xcode-15.2`",
"`macos-ventura-13.6-xcode-15.2`",
"`macos-ventura-13.4-xcode-14.3.1`"
]
},
{
"deprecated": true,
"enum": [
"default",
"macos-monterey-12.4-xcode-13.4",
"macos-monterey-12.3-xcode-13.3",
"macos-monterey-12.1-xcode-13.2"
],
"markdownEnumDescriptions": [
"This image is deprecated, please use one of the more recent images instead.",
"This image is deprecated, please use one of the more recent images instead.",
"This image is deprecated, please use one of the more recent images instead.",
"This image is deprecated, please use one of the more recent images instead."
@@ -435,12 +408,7 @@
"default": "default",
"anyOf": [
{
"enum": [
"default",
"medium",
"large",
"m-medium"
]
"enum": ["default", "medium", "large", "m-medium"]
},
{
"type": "string"
@@ -524,12 +492,7 @@
"markdownDescription": "Path to the JSON file with service account key used to authenticate with Google Play. [Learn more](https://expo.fyi/creating-google-service-account)"
},
"track": {
"enum": [
"beta",
"alpha",
"internal",
"production"
],
"enum": ["beta", "alpha", "internal", "production"],
"description": "The track of the application to use. Learn more: https://support.google.com/googleplay/android-developer/answer/9859348?hl=en",
"markdownDescription": "The [track of the application](https://support.google.com/googleplay/android-developer/answer/9859348?hl=en) to use.",
"markdownEnumDescriptions": [
@@ -540,12 +503,7 @@
]
},
"releaseStatus": {
"enum": [
"draft",
"inProgress",
"halted",
"completed"
],
"enum": ["draft", "inProgress", "halted", "completed"],
"description": "The status of a release. Learn more: https://developers.google.com/android-publisher/api-ref/rest/v3/edits.tracks",
"markdownDescription": "The status of a release. [Learn more](https://developers.google.com/android-publisher/api-ref/rest/v3/edits.tracks)",
"enumDescriptions": [