Prebuild and iOS code signing

Hello, we are in the progress of switching a react-native project over to Expo, and have successfully built locally with prebuild. The next step is updating our fastlane based GitHub Action to produce builds, therefore we are not yet using EAS Build.

The signing step in the fastlane build process requires the iOS project to be configured for CODE_SIGN_IDENTITY, PROVISIONING_PROFILE_SPECIFIER, etc.

I have not seen in the docs or in searching how this can be done without writing our own config plugin to modify project.pbxproj. I’m thinking this has probably been done somewhere but cannot find it.

Any suggestions appreciated!

-Jim, EF Tours

I have not seen in the docs or in searching how this can be done without writing our own config plugin to modify project.pbxproj. I’m thinking this has probably been done somewhere but cannot find it.

There is no config-plugin like that, this is sth that EAS Build is doing on the fly during a build.

You can either:

  • Use a bare workflow, It would mean you need to commit android/ios directories, configure what you need via xcode the same way you had in your old react-native app.
  • Stay on managed, Add android/ios to gitignore, use prebuild only for local native development and build everything using eas-cli (If you don’t want to use EAS Build builders you can use eas build --local)

I don’t recommend that, but the third possibility is you can check how EAS Build configures credentials and incorporate that code into your own process, that whole code is open source. https://github.com/expo/eas-build/blob/main/packages/build-tools/src/ios/configure.ts#L37

1 Like

Got it, thanks. We’re not ready to go EAS Build yet, our fastlane scripts do a bunch of other config stuff for us that we’re not ready to give up yet. I’ll experiment with the config plugin route and see how easy it is, the diffs don’t look too bad. It’s probably worth it to get to prebuild and not have to think about the native binaries as much.

-Jim

1 Like

Just a followup, we’re adopting expo prebuild in our workflow, in order to remove dependence on maintaining the android/ios directories. Writing config plugins gives us a single focal point for modifications to the native apps and is going well. I’m uncomfortable with the regexp approach in the “dangerous” modifications, so thought I’d post a sample in case it’s useful to anyone else:

// Compile to produce ios-signing-plugin.js:
// $ yarn tsc plugins/ios-signing-plugin.ts --skipLibCheck

import {
    ConfigPlugin,
    IOSConfig,
    withDangerousMod
} from '@expo/config-plugins'
import { promises as fs } from 'fs'

export function modifyProjectSection(contents: string): string {
    const results = []
    const lines = contents.split('\n')
    let foundSection = false
    for (const line of lines) {
        if (line.includes("Begin PBXProject section")) {
            results.push(line)
            foundSection = true
        } else if (foundSection && line.includes("13B07F861A680F5B00A75B9A = {")) {
            results.push(line)
            results.push('DevelopmentTeam = RE4S5JB5G4;')
            results.push('ProvisioningStyle = Manual;')
        } else {
            results.push(line)
        }
    }
    contents = results.join('\n')

    return contents
}

export function modifyBuildConfigSection(contents: string): string {
    const results = []
    const lines = contents.split('\n')
    let foundSection = false
    let foundDebug = false
    let foundRelease = false
    for (const line of lines) {
        if (line.includes("Begin XCBuildConfiguration section")) {
            results.push(line)
            foundSection = true
        } else if (foundSection && line.includes("13B07F941A680F5B00A75B9A /* Debug */ = {")) {
            results.push(line)
            foundDebug = true
        } else if (foundDebug && !foundRelease && line.includes("buildSettings = {")) {
            results.push(line)
            results.push('CODE_SIGN_STYLE = Manual;')
            results.push('DEVELOPMENT_TEAM = RE4S5JB5G4;')
            results.push('PROVISIONING_PROFILE_SPECIFIER = "EF On Tour TD, Development";')
        } else if (foundSection && line.includes("13B07F951A680F5B00A75B9A /* Release */ = {")) {
            results.push(line)
            foundRelease = true
        } else if (foundRelease && line.includes("buildSettings = {")) {
            results.push(line)
            results.push('CODE_SIGN_IDENTITY = "iPhone Distribution";')
            results.push('CODE_SIGN_STYLE = Manual;')
            results.push('ENABLE_BITCODE = NO;')
            results.push('DEVELOPMENT_TEAM = RE4S5JB5G4;')
            results.push('PROVISIONING_PROFILE_SPECIFIER = "EF On Tour TD, App Store";')
        } else {
            results.push(line)
        }
    }
    contents = results.join('\n')

    return contents
}

const withProjectFile: ConfigPlugin = (config) => {
    return withDangerousMod(config, [
        "ios",
        async (config) => {
            const filePath = IOSConfig.Paths.getPBXProjectPath(
                config.modRequest.projectRoot
            )
            let contents = await fs.readFile(filePath, "utf-8")
            contents = modifyProjectSection(contents)
            contents = modifyBuildConfigSection(contents)
            await fs.writeFile(filePath, contents)

            return config
        },
    ])
}

const withAppAuth: ConfigPlugin = (config) => {
    config = withProjectFile(config)
    return config
}

export default withAppAuth

It’s working pretty well, one significant test will be how much we have to tweak these plugins when we upgrade from Expo SDK 45 to 46. I expect it to be much more predictable than what we had before. With this it’s easy to say expo prebuild and see if the config plugins have done what we expected, compared to digging up old PRs and translating them to the new version.