React-native-config: Availability in Build settings and Info.plist problem

Created on 24 Sep 2019  路  31Comments  路  Source: luggit/react-native-config

where we must to add below code in step 6 of Availability in Build settings and Info.plist section:

"${SRCROOT}/../node_modules/react-native-config/ios/ReactNativeConfig/BuildXCConfig.rb" "${SRCROOT}/.." "${SRCROOT}/tmp.xcconfig"

Most helpful comment

Thanks for adding the patch to the latest release, but the plist variables on first build are still not working for me. Can someone post a working example on how to configure this correctly?

EDIT: I figured it out by myself

  1. Necessary modification in BuildDotenvConfig.rb line 28 (Pull-Request https://github.com/luggit/react-native-config/pull/457) to re-enable the creation of GeneratedInfoPlistDotEnv.h which was lost in update 0.11.7 to 0.12.0.

    - #File.delete('/tmp/envfile') if custom_env
    
    + # create header file with defines for the Info.plist preprocessor
    + info_plist_defines_objc = dotenv.map { |k, v| %Q(#define RNC_#{k}  #{v}) }.join("\n")
    +
    + # write it so the Info.plist preprocessor can access it
    + path = File.join(ENV["BUILD_DIR"], "GeneratedInfoPlistDotEnv.h")
    + File.open(path, "w") { |f| f.puts info_plist_defines_objc }
    
  2. Add this 2 scripts to a scripts folder in project root

    generate-dot-env-files.sh:

    #!/usr/bin/env bash
    
    # get script parameter
    SRC_ROOT=$1
    
    # create config files
    RNC_ROOT=./node_modules/react-native-config/ &&
    export SYMROOT=$RNC_ROOT/ios/ReactNativeConfig &&
    ruby $RNC_ROOT/ios/ReactNativeConfig/BuildDotenvConfig.rb ${SRC_ROOT}/../ ${SYMROOT}
    

    This script creates/updates necessary GeneratedDotEnv.m and GeneratedInfoPlistDotEnv.h files.

    generate-env-config.sh:

    #!/usr/bin/env bash
    
    # get script parameter
    SRC_ROOT=$1
    
    # Generate tmp.xcconfig
    "${SRC_ROOT}/../node_modules/react-native-config/ios/ReactNativeConfig/BuildXCConfig.rb" "${SRC_ROOT}/.." "${SRC_ROOT}/tmp.xcconfig"
    

    This script generates tmp.xcconfig from .env file which is used to access variables from JavaScript side.

  3. In Xcode select your main project file and add 2 External Build Tool Configurations in the Targets area

    GenerateEnvFiles:

    Build Tool: ${SRCROOT}/../scripts/generate-dot-env-files.sh
    Arguments: ${SRCROOT}
    Directory: ${SRCROOT}/..
    [x] Pass build settings in environment
    

    GenerateEnvConfigFiles:

    Build Tool: ${SRCROOT}/../scripts/generate-env-config.sh
    Arguments: ${SRCROOT}
    Directory: ${SRCROOT}/..
    [x] Pass build settings in environment
    
  4. Open your build scheme, disable option Parallelize Build and add both newly created targets before your app targets, place GenerateEnvConfigFiles on first, GenerateEnvFiles at second position.

  5. In your build scheme select Build -> Pre-Actions, set /bin/sh as Shell and add following script:

    exec > ${PROJECT_DIR}/prebuild.log 2>&1
    echo "Prebuild Script" ${BUILD_STYLE}
    
    rm "${TEMP_DIR}/Preprocessed-Info.plist"
    if [ $? -eq 0 ]; then
        echo "Removed prebuilt plist from ${TEMP_DIR}"
    fi
    

    This logs the script output to prebuild.log file in ios folder. It removes the preprocessed info.plist so variable changes gets updated on every build.

  6. Open up the Build Settings of your app target, search for preprocess and add following:

    Info.plist Other Preprocessor Flags = -traditional
    Info.plist Preprocessor Prefix File = ${BUILD_DIR}/GeneratedInfoPlistDotEnv.h
    Preprocess Info.plist File = YES
    
  7. Finally you can use your variables defined in .env file (e.g. APP_VERSION and APP_BUILD) in your Info.plist with RNC_APP_VERSION and RNC_APP_BUILD and the variables are updated on every build.

Some further thoughts:

  • Maybe there is a cleaner solution using only tmp.xcconfig but this is out of my scope
  • I remember I used ReactNativeConfig instead of GenerateEnvFiles target because the necessary files were generated on build (BuildDotenvConfig.rb is called as run script) but it didn't worked this time.

All 31 comments

In your scheme settings, Build -> Pre-actions
If you haven't followed this approach then follow these steps first and then add the line you have quoted above,

Expand the "Build" settings on left
Click "Pre-actions", and under the plus sign select "New Run Script Action"

Since the update from 0.11.7 to 0.12.0 all variables used in info.plist are only updated on the second build. The Build log shows RN-config updates the variables correct on first build, but the info.plist in the produced app is not updated correspondingly.

This was better in v0.11.7 when plist-preprocessing was possible using the GeneratedInfoPlistDotEnv.h file. As this file is not longer produced after the update (due to changes in BuildDotenvConfig.ruby now BuildDotenvConfig.rb) this convenient way of updating variables on first build is no longer available.

Any thoughs on this?

Looks like @rafaelmaeuer 's comment and #409 are related.

why was the code generating, GeneratedInfoPlistDotEnv.h removed?

GeneratedInfoPlistDotEnv.h removed?

Yep

why was the code generating,

I don't really understand the context to give an answer to this.

Yeah so on my circle ci build, I'm now running this custom script which is a temporary measure.

patches in the .env file into info.plist

#!/usr/bin/env node
const fs = require('fs');
const dotenv = require('dotenv');
const promisify = require('util').promisify;
const writeFile = promisify(fs.writeFile);
const readFile = promisify(fs.readFile);

async function main() {
  try {
    console.log('===================== THIS IS A TEMPORARY SCRIPT ===================');
    console.log('===================== UPDATING Info.plist with .env ================');
    console.log('====================================================================');
    console.log('see Issue: https://github.com/luggit/react-native-config/issues/391');

    const envfile = await readFile('./.env', 'utf-8');
    const plist = await readFile('./ios/medapp/Info.plist', 'utf-8');
    const env = dotenv.parse(Buffer.from(envfile));
    let plistupdated = plist;
    Object.entries(env).forEach(([key,value]) => {
      plistupdated = plistupdated.replace(new RegExp(`\\$\\(${key}\\)`, 'g'), value);
    });
    await writeFile('./ios/medapp/Info.plist', plistupdated, 'utf-8');
    await writeFile('./ios/medapp/Info.plist.bak', plist, 'utf-8');
  } catch (e) {
    console.error(e);
    console.error(`Unable to read .env file`);
    process.exit(1);
  }
}
main();

My temporary workaround is to remove the app and build again:

ENVFILE=.env.test node_modules/.bin/detox build --configuration ios.sim.release
rm -rf ios/build/Build/Products/Release-iphonesimulator/xxx.app
ENVFILE=.env.test node_modules/.bin/detox build --configuration ios.sim.release
ENVFILE=.env.test node_modules/.bin/detox test --configuration ios.sim.release

@export-mike I'm using Bitrise as my CI/CD platform, and your custom script worked for me, thank you. I hope a proper fix is released soon.

Hi ! If your issue is Build input file cannot be found: GeneratedDotEnv.m and if you are using pods and RN > 0.60, you can patch the podspec with this commit : https://github.com/bamlab/react-native-config/commit/05761868680ad46c9dbd1f56d7d504f056f444db

(file located at node_modules/react-native-config/react-native-config.podspec)

  s.script_phase = {
    name: 'Config codegen',
    script: %(
- set -ex
- HOST_PATH="$SRCROOT/../.."
- "${PODS_TARGET_SRCROOT}/ios/ReactNativeConfig/BuildDotenvConfig.rb" "$HOST_PATH" "${PODS_TARGET_SRCROOT}/ios/ReactNativeConfig"
- ),
+      set -ex
+      HOST_PATH="$SRCROOT/../.."
+      ${PODS_TARGET_SRCROOT}/ios/ReactNativeConfig/BuildDotenvConfig.rb $HOST_PATH ${PODS_TARGET_SRCROOT}/ios/ReactNativeConfig
+    ),
    execution_position: :before_compile,
-    input_files: ['$(SRCROOT)/ReactNativeConfig/BuildDotenvConfig.rb']
+    input_files: ['$PODS_TARGET_SRCROOT/ios/ReactNativeConfig/BuildDotenvConfig.rb'],
+    output_files: ['$PODS_TARGET_SRCROOT/ios/ReactNativeConfig/GeneratedDotEnv.m']
  }

  s.source_files = 'ios/**/*.{h,m}'

Explanation: The custom pod script generates the GeneratedDotEnv.m file at the same time XCode requires it. There is a race condition. In order to fix that, we need to tell XCode that our script outputs one file that is required in another step. Doing so, XCode new Build system will create a correct dependency graph and erase this race condition.

Source: http://www.gietal.net/blog/xcode10-and-prebuild-script-output-files-xcfilelist

@rafaelmaeuer did you end up using a workaround that worked? Currently my workaround is to build the first env of the app twice.

I didn't had the time to test any workaround yet, so I am doing the same: building the app twice is annoying but it works...

@tpucci Thanks for your suggestion (https://github.com/luggit/react-native-config/issues/391#issuecomment-571948199), it's the only thing which seems to work for me, only issue is remembering to edit the podspec if/when node_modules is rebuilt 馃槱

@tpucci Thanks for your suggestion (#391 (comment)), it's the only thing which seems to work for me, only issue is remembering to edit the podspec if/when node_modules is rebuilt

@leemcmullen I recommend that you use 'patch-package' in order not to worry about that.

@tpucci Good idea, thanks Thomas! 馃憤

anyplans to release this patch as a fix?

If anyone needs https://github.com/luggit/react-native-config/issues/391#issuecomment-571948199 as a permanent patch:

  1. Add patch-package
yarn add --dev patch-package # or npm install -D patch-package
  1. Add patch-package to postinstall step in package.json
...
  "scripts": {
    ...
    "postinstall": "patch-package",
    ...
    },
...

In our script we also need react-native-inhibit-warnings and jetifier

"postinstall": "react-native-inhibit-warnings && jetifier && patch-package",
  1. Create the patchfile at patches/react-native-config+1.0.0.patch
diff --git a/node_modules/react-native-config/react-native-config.podspec b/node_modules/react-native-config/react-native-config.podspec
index 918eb47..e0dfec7 100644
--- a/node_modules/react-native-config/react-native-config.podspec
+++ b/node_modules/react-native-config/react-native-config.podspec
@@ -25,7 +25,8 @@ HOST_PATH="$SRCROOT/../.."
 "${PODS_TARGET_SRCROOT}/ios/ReactNativeConfig/BuildDotenvConfig.rb" "$HOST_PATH" "${PODS_TARGET_SRCROOT}/ios/ReactNativeConfig"
 ),
     execution_position: :before_compile,
-    input_files: ['$(SRCROOT)/ReactNativeConfig/BuildDotenvConfig.rb']
+    input_files: ['$PODS_TARGET_SRCROOT/ios/ReactNativeConfig/BuildDotenvConfig.rb'],
+    output_files: ['$PODS_TARGET_SRCROOT/ios/ReactNativeConfig/GeneratedDotEnv.m']
   }

   s.source_files = 'ios/**/*.{h,m}'

(Note I removed whitespace changes from @tpucci 's patch)

  1. yarn or npm install
  2. cd ios && pod install --repo-update && cd -

Confirmed working on:

react-native: 0.61.5
react-native-config: 1.0.0

@jacobcabantomski-ct any reason why this needs to stay as a match and not merged, I've seen others fork the project just to make this change

Thanks for adding the patch to the latest release, but the plist variables on first build are still not working for me. Can someone post a working example on how to configure this correctly?

EDIT: I figured it out by myself

  1. Necessary modification in BuildDotenvConfig.rb line 28 (Pull-Request https://github.com/luggit/react-native-config/pull/457) to re-enable the creation of GeneratedInfoPlistDotEnv.h which was lost in update 0.11.7 to 0.12.0.

    - #File.delete('/tmp/envfile') if custom_env
    
    + # create header file with defines for the Info.plist preprocessor
    + info_plist_defines_objc = dotenv.map { |k, v| %Q(#define RNC_#{k}  #{v}) }.join("\n")
    +
    + # write it so the Info.plist preprocessor can access it
    + path = File.join(ENV["BUILD_DIR"], "GeneratedInfoPlistDotEnv.h")
    + File.open(path, "w") { |f| f.puts info_plist_defines_objc }
    
  2. Add this 2 scripts to a scripts folder in project root

    generate-dot-env-files.sh:

    #!/usr/bin/env bash
    
    # get script parameter
    SRC_ROOT=$1
    
    # create config files
    RNC_ROOT=./node_modules/react-native-config/ &&
    export SYMROOT=$RNC_ROOT/ios/ReactNativeConfig &&
    ruby $RNC_ROOT/ios/ReactNativeConfig/BuildDotenvConfig.rb ${SRC_ROOT}/../ ${SYMROOT}
    

    This script creates/updates necessary GeneratedDotEnv.m and GeneratedInfoPlistDotEnv.h files.

    generate-env-config.sh:

    #!/usr/bin/env bash
    
    # get script parameter
    SRC_ROOT=$1
    
    # Generate tmp.xcconfig
    "${SRC_ROOT}/../node_modules/react-native-config/ios/ReactNativeConfig/BuildXCConfig.rb" "${SRC_ROOT}/.." "${SRC_ROOT}/tmp.xcconfig"
    

    This script generates tmp.xcconfig from .env file which is used to access variables from JavaScript side.

  3. In Xcode select your main project file and add 2 External Build Tool Configurations in the Targets area

    GenerateEnvFiles:

    Build Tool: ${SRCROOT}/../scripts/generate-dot-env-files.sh
    Arguments: ${SRCROOT}
    Directory: ${SRCROOT}/..
    [x] Pass build settings in environment
    

    GenerateEnvConfigFiles:

    Build Tool: ${SRCROOT}/../scripts/generate-env-config.sh
    Arguments: ${SRCROOT}
    Directory: ${SRCROOT}/..
    [x] Pass build settings in environment
    
  4. Open your build scheme, disable option Parallelize Build and add both newly created targets before your app targets, place GenerateEnvConfigFiles on first, GenerateEnvFiles at second position.

  5. In your build scheme select Build -> Pre-Actions, set /bin/sh as Shell and add following script:

    exec > ${PROJECT_DIR}/prebuild.log 2>&1
    echo "Prebuild Script" ${BUILD_STYLE}
    
    rm "${TEMP_DIR}/Preprocessed-Info.plist"
    if [ $? -eq 0 ]; then
        echo "Removed prebuilt plist from ${TEMP_DIR}"
    fi
    

    This logs the script output to prebuild.log file in ios folder. It removes the preprocessed info.plist so variable changes gets updated on every build.

  6. Open up the Build Settings of your app target, search for preprocess and add following:

    Info.plist Other Preprocessor Flags = -traditional
    Info.plist Preprocessor Prefix File = ${BUILD_DIR}/GeneratedInfoPlistDotEnv.h
    Preprocess Info.plist File = YES
    
  7. Finally you can use your variables defined in .env file (e.g. APP_VERSION and APP_BUILD) in your Info.plist with RNC_APP_VERSION and RNC_APP_BUILD and the variables are updated on every build.

Some further thoughts:

  • Maybe there is a cleaner solution using only tmp.xcconfig but this is out of my scope
  • I remember I used ReactNativeConfig instead of GenerateEnvFiles target because the necessary files were generated on build (BuildDotenvConfig.rb is called as run script) but it didn't worked this time.

@rafaelmaeuer Can your steps be added to the README, or should a more automatic solution be investigated?

If this is wanted I can update the readme to include my solution. @luancurti what do you think?

working on: "react-native-config": "1.2.1",

If this is wanted I can update the readme to include my solution. @luancurti what do you think?

I think it's a good idea to add this throubleshoting in the README @rafaelmaeuer feel free to make this PR

This is my solution :

  • react-native 0.61.5
  • react-native-config 1.3.3
  • Create a variable on config.xcconfig file (ex: APP_NAME_TEST = AppNameTest)
  • Call it on info.plist : ${APP_NAME_TEST} --> Build
  • Try with another variable on your env file(staging, production...) --> Rebuild
  • Enjoy !!!
    ---> Hope to help you.

Hi ! If your issue is Build input file cannot be found: GeneratedDotEnv.m and if you are using pods and RN > 0.60, you can patch the podspec with this commit : bamlab@0576186

(file located at node_modules/react-native-config/react-native-config.podspec)

  s.script_phase = {
    name: 'Config codegen',
    script: %(
- set -ex
- HOST_PATH="$SRCROOT/../.."
- "${PODS_TARGET_SRCROOT}/ios/ReactNativeConfig/BuildDotenvConfig.rb" "$HOST_PATH" "${PODS_TARGET_SRCROOT}/ios/ReactNativeConfig"
- ),
+      set -ex
+      HOST_PATH="$SRCROOT/../.."
+      ${PODS_TARGET_SRCROOT}/ios/ReactNativeConfig/BuildDotenvConfig.rb $HOST_PATH ${PODS_TARGET_SRCROOT}/ios/ReactNativeConfig
+    ),
    execution_position: :before_compile,
-    input_files: ['$(SRCROOT)/ReactNativeConfig/BuildDotenvConfig.rb']
+    input_files: ['$PODS_TARGET_SRCROOT/ios/ReactNativeConfig/BuildDotenvConfig.rb'],
+    output_files: ['$PODS_TARGET_SRCROOT/ios/ReactNativeConfig/GeneratedDotEnv.m']
  }

  s.source_files = 'ios/**/*.{h,m}'

_Explanation:_ The custom pod script generates the GeneratedDotEnv.m file at the same time XCode requires it. There is a race condition. In order to fix that, we need to tell XCode that our script outputs one file that is required in another step. Doing so, XCode new Build system will create a correct dependency graph and erase this race condition.

_Source:_ http://www.gietal.net/blog/xcode10-and-prebuild-script-output-files-xcfilelist

Worked for me.

I'm using pod generated xcconfig files, so I created a post_install that appends #include? "tmp.xcconfig"

post_install do |installer|
  puts "Updating Targets to include react-native-config env variables"
  installer.generated_aggregate_targets.each do |target|
    ["Debug", "Release"].each do |config|
      rpath = target.xcconfig_relative_path(config)
      puts "    updating #{target.name} :#{config}"
      open(rpath, 'a') { |f|
          f.puts "#include? \"tmp.xcconfig\""
      }
    end
  end
end

I was having the same issue but I found that my bash script was not properly written in the prebuild scripts. It should be like

cp ${PROJECT_DIR}/../.env.dev ${PROJECT_DIR}/../.env

"${SRCROOT}/../node_modules/react-native-config/ios/ReactNativeConfig/BuildXCConfig.rb" "${SRCROOT}/.." "${SRCROOT}/tmp.xcconfig"

also, you have to write in the inputshell -> /bin/sh
and in Provide build settings from -> "your target"

It was not very clear from README instructions where to add the script to generate tmp.xcconfig, so I mistakenly added it to the Build Phases under my application target. As it turned out it was too late: Info.plist is copied to the BUILD_DIR as a first step of the build and tmp.xcconfig is generated after that. As a result on the first build values from the tmp.xcconfig are not picked up and changes to the .env file are only picked up by the second build after the change is made.

The correct place where to add BuildXCConfig.rb is in Edit scheme... -> Build -> Pre-actions. Also make sure to select your target for "Provide build settings from".

image

Otherwise the approach seems to work correctly and you don't need to make loads of manual work as described in https://github.com/luggit/react-native-config/issues/391#issuecomment-632331803.

PS Note that variables don't have RNC_ prefix as with the legacy approach involving the Info.plist preprocessing.

UPD

Apparently one needs to setup same Pre-Action for the Archive schema, so values are available during the Archive build. And likely for every single schema you use. So I decided to to run this script manually before invoking Xcode to avoid maintaining same code in multiple places.

PS Note that variables don't have RNC_ prefix as with the legacy approach involving the Info.plist preprocessing.

Ty bro! That was my issue

This is my solution

This is my pre action build script, I use /tmp/envfile where I copy my current env file based on a User-defined variable, ie ${RNCONFIG_ENVIRONMENT}

# Type a script or drag a script file from your workspace to insert its path.
# exec > ${PROJECT_DIR}/prebuil#d.log 2>&1
# ie, copy .env.development  to /tmp/envfile
cp "${SRCROOT}/../.env.${RNCONFIG_ENVIRONMENT}" "/tmp/envfile"
# same as always, not changes here
"${SRCROOT}/../node_modules/react-native-config/ios/ReactNativeConfig/BuildXCConfig.rb" "${SRCROOT}/.." "${SRCROOT}/tmp.xcconfig"

To setup a user defined variable, do this,
Xcode -> Left panel (select top root project) -> Targets (your project) -> Build settings -> (click) Plus icon -> Add user defined settings

Screen Shot 2020-10-21 at 18 41 49

Add a variable called RNCONFIG_ENVIRONMENT with both values, for debug and release

Screen Shot 2020-10-21 at 17 25 15

Remember to create both env files in your root react native project, ie, .env.development and .env.production

The final changed is to modified the file node_modules/react-native-config/ios/ReactNativeConfig/ReadDotEnv.rb

diff --git a/old.txt b/new.txt
index 4e9fd89..51bd95e 100644
--- a/old.txt
+++ b/new.txt
@@ -14,28 +14,30 @@ def read_dot_env(envs_root)
   # pick a custom env file if set
   if File.exist?('/tmp/envfile')
     custom_env = true
-    file = File.read('/tmp/envfile').strip
+    raw = File.read('/tmp/envfile').strip
   else
     custom_env = false
     file = ENV['ENVFILE'] || defaultEnvFile
   end
-
   dotenv = begin
     # https://regex101.com/r/cbm5Tp/1
     dotenv_pattern = /^(?:export\s+|)(?<key>[[:alnum:]_]+)\s*=\s*((?<quote>["'])?(?<val>.*?[^\\])\k<quote>?|)$/

-    path = File.expand_path(File.join(envs_root, file.to_s))
-    if File.exist?(path)
-      raw = File.read(path)
-    elsif File.exist?(file)
-      raw = File.read(file)
-    else
-      defaultEnvPath = File.expand_path(File.join(envs_root, "#{defaultEnvFile}"))
-      unless File.exist?(defaultEnvPath)
-        # try as absolute path
-        defaultEnvPath = defaultEnvFile
+    unless custom_env
+      path = File.expand_path(File.join(envs_root, file.to_s))
+      if File.exist?(path)
+        raw = File.read(path)
+      elsif File.exist?(file)
+        raw = File.read(file)
+      else
+        defaultEnvPath = File.expand_path(File.join(envs_root, "#{defaultEnvFile}"))
+        puts defaultEnvPath
+        unless File.exist?(defaultEnvPath)
+          # try as absolute path
+          defaultEnvPath = defaultEnvFile
+        end
+        raw = File.read(defaultEnvPath)
       end
-      raw = File.read(defaultEnvPath)
     end

     raw.split("\n").inject({}) do |h, line|

full file

I recommend to use patch-package to build your own patch.

It was not very clear from README instructions where to add the script to generate tmp.xcconfig, so I mistakenly added it to the Build Phases under my application target. As it turned out it was too late: Info.plist is copied to the BUILD_DIR as a first step of the build and tmp.xcconfig is generated after that. As a result on the first build values from the tmp.xcconfig are not picked up and changes to the .env file are only picked up by the second build after the change is made.

The correct place where to add BuildXCConfig.rb is in Edit scheme... -> Build -> Pre-actions. Also make sure to select your target for "Provide build settings from".

image

Otherwise the approach seems to work correctly and you don't need to make loads of manual work as described in #391 (comment).

PS Note that variables don't have RNC_ prefix as with the legacy approach involving the Info.plist preprocessing.

UPD

Apparently one needs to setup same Pre-Action for the Archive schema, so values are available during the Archive build. And likely for every single schema you use. So I decided to to run this script manually before invoking Xcode to avoid maintaining same code in multiple places.

This worked for me as well. Im developing with multiple targets (dev, stage, prod). So I had to add Pre-actions to every target.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Quadriphobs1 picture Quadriphobs1  路  3Comments

sonlexqt picture sonlexqt  路  4Comments

Husnain-Asharf picture Husnain-Asharf  路  3Comments

jemise111 picture jemise111  路  4Comments

himanshu-satija picture himanshu-satija  路  3Comments