UNPKG

expo-build-properties

Version:

Config plugin to customize native build properties on prebuild

736 lines (696 loc) 23.9 kB
import Ajv, { JSONSchemaType } from 'ajv'; import semver from 'semver'; /** * The minimal supported versions. These values should align to SDK. * @ignore */ const EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS = { android: { minSdkVersion: 21, compileSdkVersion: 31, targetSdkVersion: 31, kotlinVersion: '1.6.10', }, ios: { deploymentTarget: '15.1', }, }; /** * Interface representing base build properties configuration. */ export interface PluginConfigType { /** * Interface representing available configuration for Android native build properties. * @platform android */ android?: PluginConfigTypeAndroid; /** * Interface representing available configuration for iOS native build properties. * @platform ios */ ios?: PluginConfigTypeIos; } /** * Interface representing available configuration for Android native build properties. * @platform android */ export interface PluginConfigTypeAndroid { /** * @deprecated Use app config [`newArchEnabled`](https://docs.expo.dev/versions/latest/config/app/#newarchenabled) instead. * Enable React Native new architecture for Android platform. */ newArchEnabled?: boolean; /** * Override the default `minSdkVersion` version number in **build.gradle**. * */ minSdkVersion?: number; /** * Override the default `compileSdkVersion` version number in **build.gradle**. */ compileSdkVersion?: number; /** * Override the default `targetSdkVersion` version number in **build.gradle**. */ targetSdkVersion?: number; /** * Override the default `buildToolsVersion` version number in **build.gradle**. */ buildToolsVersion?: string; /** * Override the Kotlin version used when building the app. */ kotlinVersion?: string; /** * Enable [Proguard or R8](https://developer.android.com/studio/build/shrink-code) in release builds to obfuscate Java code and reduce app size. */ enableProguardInReleaseBuilds?: boolean; /** * Enable [`shrinkResources`](https://developer.android.com/studio/build/shrink-code#shrink-resources) in release builds to remove unused resources from the app. * This property should be used in combination with `enableProguardInReleaseBuilds`. */ enableShrinkResourcesInReleaseBuilds?: boolean; /** * Enable [`crunchPngs`](https://developer.android.com/topic/performance/reduce-apk-size#crunch) in release builds to optimize PNG files. * This property is enabled by default, but "might inflate PNG files that are already compressed", so you may want to disable it if you do your own PNG optimization. * * @default true */ enablePngCrunchInReleaseBuilds?: boolean; /** * Append custom [Proguard rules](https://www.guardsquare.com/manual/configuration/usage) to **android/app/proguard-rules.pro**. */ extraProguardRules?: string; /** * Interface representing available configuration for Android Gradle plugin [`PackagingOptions`](https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/dsl/PackagingOptions). */ packagingOptions?: PluginConfigTypeAndroidPackagingOptions; /** * Enable the Network Inspector. * * @default true */ networkInspector?: boolean; // For the implementation details, this property is actually handled by `expo-modules-autolinking` // not the config-plugins inside `expo-build-properties` /** * Add extra maven repositories to all gradle projects. * * Takes an array of objects or strings. * Strings are passed as the `url` property of the object with no credentials or authentication scheme. * * This adds the following code to **android/build.gradle**: * ```groovy * allprojects { * repositories { * maven { * url "https://foo.com/maven-releases" * } * } * ``` * * By using an `AndroidMavenRepository` object, you can specify credentials and an authentication scheme. * ```groovy * allprojects { * repositories { * maven { * url "https://foo.com/maven-releases" * credentials { * username = "bar" * password = "baz" * } * authentication { * basic(BasicAuthentication) * } * } * } * } * ``` * * @see [Gradle documentation](https://docs.gradle.org/current/userguide/declaring_repositories.html#sec:case-for-maven) */ extraMavenRepos?: (AndroidMavenRepository | string)[]; /** * Indicates whether the app intends to use cleartext network traffic. * * @default false * * @see [Android documentation](https://developer.android.com/guide/topics/manifest/application-element#usesCleartextTraffic) */ usesCleartextTraffic?: boolean; /** * Instructs the Android Gradle plugin to compress native libraries in the APK using the legacy packaging system. * * @default false * * @see [Android documentation](https://developer.android.com/build/releases/past-releases/agp-4-2-0-release-notes#compress-native-libs-dsl) */ useLegacyPackaging?: boolean; /** * Specifies the set of other apps that an app intends to interact with. These other apps are specified by package name, * by intent signature, or by provider authority. * * @see [Android documentation](https://developer.android.com/guide/topics/manifest/queries-element) */ manifestQueries?: PluginConfigTypeAndroidQueries; /** * Changes the apps theme to a DayNight variant to correctly support dark mode. * * @see [Android documentation](https://developer.android.com/develop/ui/views/theming/darktheme) */ useDayNightTheme?: boolean; /** * Enable JavaScript Bundle compression. Turning this on will result in a smaller APK size but may have slower app startup times. * * @see [Faster App Startup](https://reactnative.dev/blog/2025/04/08/react-native-0.79#android-faster-app-startup) * * @default false */ enableBundleCompression?: boolean; } // @docsMissing /** * @platform android */ export interface AndroidMavenRepository { /** * The URL of the Maven repository. */ url: string; /** * The credentials to use when accessing the Maven repository. * May be of type PasswordCredentials, HttpHeaderCredentials, or AWSCredentials. * * @see The authentication schemes section of [Gradle documentation](https://docs.gradle.org/current/userguide/declaring_repositories.html#sec:authentication_schemes) for more information. */ credentials?: AndroidMavenRepositoryCredentials; /** * The authentication scheme to use when accessing the Maven repository. */ authentication?: 'basic' | 'digest' | 'header'; } // @docsMissing /** * @platform android */ export interface AndroidMavenRepositoryPasswordCredentials { username: string; password: string; } // @docsMissing /** * @platform android */ export interface AndroidMavenRepositoryHttpHeaderCredentials { name: string; value: string; } // @docsMissing /** * @platform android */ export interface AndroidMavenRepositoryAWSCredentials { accessKey: string; secretKey: string; sessionToken?: string; } // @docsMissing /** * @platform android */ export type AndroidMavenRepositoryCredentials = | AndroidMavenRepositoryPasswordCredentials | AndroidMavenRepositoryHttpHeaderCredentials | AndroidMavenRepositoryAWSCredentials; /** * Interface representing available configuration for iOS native build properties. * @platform ios */ export interface PluginConfigTypeIos { /** * @deprecated Use app config [`newArchEnabled`](https://docs.expo.dev/versions/latest/config/app/#newarchenabled) instead. * Enable React Native new architecture for iOS platform. */ newArchEnabled?: boolean; /** * Override the default iOS "Deployment Target" version in the following projects: * - in CocoaPods projects, * - `PBXNativeTarget` with "com.apple.product-type.application" `productType` in the app project. */ deploymentTarget?: string; /** * Enable [`use_frameworks!`](https://guides.cocoapods.org/syntax/podfile.html#use_frameworks_bang) * in `Podfile` to use frameworks instead of static libraries for Pods. */ useFrameworks?: 'static' | 'dynamic'; /** * Enable the Network Inspector. * * @default true */ networkInspector?: boolean; // For the implementation details, this property is actually handled by `expo-modules-autolinking` // but not the config-plugins inside `expo-build-properties`. /** * Add extra CocoaPods dependencies for all targets. * * This configuration is responsible for adding the new Pod entries to **ios/Podfile**. * * @example * Creating entry in the configuration like below: * ```json * [ * { * name: "Protobuf", * version: "~> 3.14.0", * } * ] * ``` * Will produce the following entry in the generated **ios/Podfile**: * ```ruby * pod 'Protobuf', '~> 3.14.0' * ``` */ extraPods?: ExtraIosPodDependency[]; /** * Enable C++ compiler cache for iOS builds. * * This speeds up compiling C++ code by caching the results of previous compilations. * * @see [React Native's documentation on local caches](https://reactnative.dev/docs/build-speed#local-caches) and * [Ccache documentation](https://ccache.dev/). */ ccacheEnabled?: boolean; /** * Enable aggregation of Privacy Manifests (`PrivacyInfo.xcprivacy`) from * CocoaPods resource bundles. If enabled, the manifests will be merged into a * single file. If not enabled, developers will need to manually aggregate them. * * @see [Privacy manifests](https://docs.expo.dev/guides/apple-privacy/) guide * and [Apple's documentation on Privacy manifest files](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files). */ privacyManifestAggregationEnabled?: boolean; } /** * Interface representing extra CocoaPods dependency. * @see [Podfile syntax reference](https://guides.cocoapods.org/syntax/podfile.html#pod) * @platform ios */ export interface ExtraIosPodDependency { /** * Name of the pod. */ name: string; /** * Version of the pod. * CocoaPods supports various [versioning options](https://guides.cocoapods.org/using/the-podfile.html#pod). * @example * ``` * ~> 0.1.2 * ``` */ version?: string; /** * Build configurations for which the pod should be installed. * @example * ``` * ['Debug', 'Release'] * ``` */ configurations?: string[]; /** * Whether this pod should use modular headers. */ modular_headers?: boolean; /** * Custom source to search for this dependency. * @example * ``` * https://github.com/CocoaPods/Specs.git * ``` */ source?: string; /** * Custom local filesystem path to add the dependency. * @example * ``` * ~/Documents/AFNetworking * ``` */ path?: string; /** * Custom podspec path. * @example * ```https://example.com/JSONKit.podspec``` */ podspec?: string; /** * Test specs can be optionally included via the :testspecs option. By default, none of a Pod's test specs are included. * @example * ``` * ['UnitTests', 'SomeOtherTests'] * ``` */ testspecs?: string[]; /** * Use the bleeding edge version of a Pod. * * @example * ```json * { * "name": "AFNetworking", * "git": "https://github.com/gowalla/AFNetworking.git", * "tag": "0.7.0" * } * ``` * * This acts like to add this pod dependency statement: * * ```rb * pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :tag => '0.7.0' * ``` */ git?: string; /** * The git branch to fetch. See the `git` property for more information. */ branch?: string; /** * The git tag to fetch. See the `git` property for more information. */ tag?: string; /** * The git commit to fetch. See the `git` property for more information. */ commit?: string; } /** * Interface representing available configuration for Android Gradle plugin [`PackagingOptions`](https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/dsl/PackagingOptions). * @platform android */ export interface PluginConfigTypeAndroidPackagingOptions { /** * Array of patterns for native libraries where only the first occurrence is packaged in the APK. */ pickFirst?: string[]; /** * Array of patterns for native libraries that should be excluded from being packaged in the APK. */ exclude?: string[]; /** * Array of patterns for native libraries where all occurrences are concatenated and packaged in the APK. */ merge?: string[]; /** * Array of patterns for native libraries that should not be stripped of debug symbols. */ doNotStrip?: string[]; } // @docsMissing /** * @platform android */ export interface PluginConfigTypeAndroidQueries { /** * Specifies one or more apps that your app intends to access. These other apps might integrate with your app, or your app might use services that these other apps provide. */ package?: string[]; /** * Specifies an intent filter signature. Your app can discover other apps that have matching `<intent-filter>` elements. * These intents have restrictions compared to typical intent filter signatures. * * @see [Android documentation](https://developer.android.com/training/package-visibility/declaring#intent-filter-signature) for more information. */ intent?: PluginConfigTypeAndroidQueriesIntent[]; /** * Specifies one or more content provider authorities. Your app can discover other apps whose content providers use the specified authorities. * There are some restrictions on the options that you can include in this `<provider>` element, compared to a typical `<provider>` manifest element. * You may only specify the `android:authorities` attribute. */ provider?: string[]; } // @docsMissing /** * @platform android */ export interface PluginConfigTypeAndroidQueriesIntent { /** * A string naming the action to perform. Usually one of the platform-defined values, such as `SEND` or `VIEW`. * * @see [Android documentation](https://developer.android.com/guide/topics/manifest/action-element) for more information. */ action?: string; /** * A description of the data associated with the intent. */ data?: PluginConfigTypeAndroidQueriesData; /** * Provides an additional way to characterize the activity handling the intent, * usually related to the user gesture or location from which it's started. */ category?: string | string[]; } // @docsMissing /** * @platform android */ export interface PluginConfigTypeAndroidQueriesData { /** * Specify a URI scheme that is handled */ scheme?: string; /** * Specify a URI authority host that is handled */ host?: string; /** * Specify a MIME type that is handled */ mimeType?: string; } const schema: JSONSchemaType<PluginConfigType> = { type: 'object', properties: { android: { type: 'object', properties: { newArchEnabled: { type: 'boolean', nullable: true }, minSdkVersion: { type: 'integer', nullable: true }, compileSdkVersion: { type: 'integer', nullable: true }, targetSdkVersion: { type: 'integer', nullable: true }, buildToolsVersion: { type: 'string', nullable: true }, kotlinVersion: { type: 'string', nullable: true }, enableProguardInReleaseBuilds: { type: 'boolean', nullable: true }, enableShrinkResourcesInReleaseBuilds: { type: 'boolean', nullable: true }, enablePngCrunchInReleaseBuilds: { type: 'boolean', nullable: true }, extraProguardRules: { type: 'string', nullable: true }, packagingOptions: { type: 'object', properties: { pickFirst: { type: 'array', items: { type: 'string' }, nullable: true }, exclude: { type: 'array', items: { type: 'string' }, nullable: true }, merge: { type: 'array', items: { type: 'string' }, nullable: true }, doNotStrip: { type: 'array', items: { type: 'string' }, nullable: true }, }, nullable: true, }, networkInspector: { type: 'boolean', nullable: true }, extraMavenRepos: { type: 'array', items: { type: ['string', 'object'], anyOf: [ { type: 'string', nullable: false }, { type: 'object', required: ['url'], properties: { url: { type: 'string', nullable: false }, credentials: { type: 'object', oneOf: [ { type: 'object', properties: { username: { type: 'string' }, password: { type: 'string' }, }, required: ['username', 'password'], additionalProperties: false, }, { type: 'object', properties: { name: { type: 'string' }, value: { type: 'string' }, }, required: ['name', 'value'], additionalProperties: false, }, { type: 'object', properties: { accessKey: { type: 'string' }, secretKey: { type: 'string' }, sessionToken: { type: 'string', nullable: true }, }, required: ['accessKey', 'secretKey'], additionalProperties: false, }, ], nullable: true, }, authentication: { type: 'string', enum: ['basic', 'digest', 'header'], nullable: true, }, }, additionalProperties: false, }, ], }, nullable: true, }, usesCleartextTraffic: { type: 'boolean', nullable: true }, useLegacyPackaging: { type: 'boolean', nullable: true }, useDayNightTheme: { type: 'boolean', nullable: true }, manifestQueries: { type: 'object', properties: { package: { type: 'array', items: { type: 'string' }, minItems: 1, nullable: true }, intent: { type: 'array', items: { type: 'object', properties: { action: { type: 'string', nullable: true }, data: { type: 'object', properties: { scheme: { type: 'string', nullable: true }, host: { type: 'string', nullable: true }, mimeType: { type: 'string', nullable: true }, }, nullable: true, }, category: { type: 'array', items: { type: 'string' }, nullable: true }, }, }, nullable: true, }, provider: { type: 'array', items: { type: 'string' }, minItems: 1, nullable: true }, }, nullable: true, }, enableBundleCompression: { type: 'boolean', nullable: true }, }, nullable: true, }, ios: { type: 'object', properties: { newArchEnabled: { type: 'boolean', nullable: true }, deploymentTarget: { type: 'string', pattern: '\\d+\\.\\d+', nullable: true }, useFrameworks: { type: 'string', enum: ['static', 'dynamic'], nullable: true }, networkInspector: { type: 'boolean', nullable: true }, ccacheEnabled: { type: 'boolean', nullable: true }, privacyManifestAggregationEnabled: { type: 'boolean', nullable: true }, extraPods: { type: 'array', items: { type: 'object', required: ['name'], properties: { name: { type: 'string' }, version: { type: 'string', nullable: true }, configurations: { type: 'array', items: { type: 'string' }, nullable: true }, modular_headers: { type: 'boolean', nullable: true }, source: { type: 'string', nullable: true }, path: { type: 'string', nullable: true }, podspec: { type: 'string', nullable: true }, testspecs: { type: 'array', items: { type: 'string' }, nullable: true }, git: { type: 'string', nullable: true }, branch: { type: 'string', nullable: true }, tag: { type: 'string', nullable: true }, commit: { type: 'string', nullable: true }, }, }, nullable: true, }, }, nullable: true, }, }, }; // note(Kudo): For the implementation, we check items one by one because Ajv does not well support custom error message. /** * Checks if specified versions meets Expo minimal supported versions. * Will throw error message whenever there are invalid versions. * * @param config The validated config passed from Ajv. * @ignore */ function maybeThrowInvalidVersions(config: PluginConfigType) { const checkItems = [ { name: 'android.minSdkVersion', configVersion: config.android?.minSdkVersion, minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.minSdkVersion, }, { name: 'android.compileSdkVersion', configVersion: config.android?.compileSdkVersion, minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.compileSdkVersion, }, { name: 'android.targetSdkVersion', configVersion: config.android?.targetSdkVersion, minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.targetSdkVersion, }, { name: 'android.kotlinVersion', configVersion: config.android?.kotlinVersion, minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.kotlinVersion, }, { name: 'ios.deploymentTarget', configVersion: config.ios?.deploymentTarget, minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.ios.deploymentTarget, }, ]; for (const { name, configVersion, minimalVersion } of checkItems) { if ( typeof configVersion === 'number' && typeof minimalVersion === 'number' && configVersion < minimalVersion ) { throw new Error(`\`${name}\` needs to be at least version ${minimalVersion}.`); } if ( typeof configVersion === 'string' && typeof minimalVersion === 'string' && semver.lt(semver.coerce(configVersion) ?? '0.0.0', semver.coerce(minimalVersion) ?? '0.0.0') ) { throw new Error(`\`${name}\` needs to be at least version ${minimalVersion}.`); } } } /** * @ignore */ export function validateConfig(config: any): PluginConfigType { const validate = new Ajv({ allowUnionTypes: true }).compile(schema); if (!validate(config)) { throw new Error('Invalid expo-build-properties config: ' + JSON.stringify(validate.errors)); } maybeThrowInvalidVersions(config); if ( config.android?.enableShrinkResourcesInReleaseBuilds === true && config.android?.enableProguardInReleaseBuilds !== true ) { throw new Error( '`android.enableShrinkResourcesInReleaseBuilds` requires `android.enableProguardInReleaseBuilds` to be enabled.' ); } return config; }