Merge pull request #349 from maxkomarychev/xcconfig

Shorter integration for ios + cocoapods issues are fixed
This commit is contained in:
Jordan Brown 2019-06-26 15:27:03 -07:00 committed by GitHub
commit 89a602bf8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 171 additions and 119 deletions

View File

@ -4,7 +4,6 @@ Module to expose config variables to your javascript code in React Native, suppo
Bring some [12 factor](http://12factor.net/config) love to your mobile apps! Bring some [12 factor](http://12factor.net/config) love to your mobile apps!
## Basic Usage ## Basic Usage
Create a new file `.env` in the root of your React Native app: Create a new file `.env` in the root of your React Native app:
@ -17,15 +16,14 @@ GOOGLE_MAPS_API_KEY=abcdefgh
Then access variables defined there from your app: Then access variables defined there from your app:
```js ```js
import Config from 'react-native-config' import Config from "react-native-config";
Config.API_URL // 'https://myapi.com' Config.API_URL; // 'https://myapi.com'
Config.GOOGLE_MAPS_API_KEY // 'abcdefgh' Config.GOOGLE_MAPS_API_KEY; // 'abcdefgh'
``` ```
Keep in mind this module doesn't obfuscate or encrypt secrets for packaging, so **do not store sensitive keys in `.env`**. It's [basically impossible to prevent users from reverse engineering mobile app secrets](https://rammic.github.io/2015/07/28/hiding-secrets-in-android-apps/), so design your app (and APIs) with that in mind. Keep in mind this module doesn't obfuscate or encrypt secrets for packaging, so **do not store sensitive keys in `.env`**. It's [basically impossible to prevent users from reverse engineering mobile app secrets](https://rammic.github.io/2015/07/28/hiding-secrets-in-android-apps/), so design your app (and APIs) with that in mind.
## Setup ## Setup
Install the package: Install the package:
@ -40,6 +38,11 @@ Link the library:
$ react-native link react-native-config $ react-native link react-native-config
``` ```
if cocoapods are used in the project then pod has to be installed as well:
```
(cd ios; pod install)
```
### Extra step for Android ### Extra step for Android
@ -50,17 +53,6 @@ You'll also need to manually apply a plugin to your app, from `android/app/build
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle" apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
``` ```
### Extra step for iOS (support Info.plist)
* Go to your project -> Build Settings -> All
* Search for "preprocess"
* Set `Preprocess Info.plist File` to `Yes`
* Set `Info.plist Preprocessor Prefix File` to `${BUILD_DIR}/GeneratedInfoPlistDotEnv.h`
* Set `Info.plist Other Preprocessor Flags` to `-traditional`
* If you don't see those settings, verify that "All" is selected at the top (instead of "Basic")
#### Advanced Android Setup #### Advanced Android Setup
In `android/app/build.gradle`, if you use `applicationIdSuffix` or `applicationId` that is different from the package name indicated in `AndroidManifest.xml` in `<manifest package="...">` tag, for example, to support different build variants: In `android/app/build.gradle`, if you use `applicationIdSuffix` or `applicationId` that is different from the package name indicated in `AndroidManifest.xml` in `<manifest package="...">` tag, for example, to support different build variants:
@ -125,14 +117,35 @@ NSString *apiUrl = [ReactNativeConfig envFor:@"API_URL"];
NSDictionary *config = [ReactNativeConfig env]; NSDictionary *config = [ReactNativeConfig env];
``` ```
They're also available for configuration in `Info.plist`, by prepending `__RN_CONFIG_` to their name: #### Availability in Build settings and Info.plist
With one extra step environment values can be exposed to "Info.plist" and Build settings in the native project.
1. click on the file tree and create new file of type XCConfig
![img](./readme-pics/1.ios_new_file.png)
![img](./readme-pics/2.ios_file_type.png)
2. save it under `ios` folder as "Config.xcconfig" with the following content:
``` ```
__RN_CONFIG_API_URL #include? "tmp.xcconfig"
``` ```
Note: Requires specific setup (see below) and a `Product > Clean` is required after changing the values to see the updated values. 3. add the following to your ".gitignore":
```
# react-native-config codegen
ios/tmp.xcconfig
```
4. go to project settings
5. apply config to your configurations
![img](./readme-pics/3.ios_apply_config.png)
6. create new build phase for the scheme which will generate "tmp.xcconfig" before each build exposing values to Build Settings and Info.plist (this snippet has to be placed after "echo ... > tmp/envfile" if [approach explained below](#ios-multi-scheme) is used)
```
"${SRCROOT}/../node_modules/react-native-config/ios/ReactNativeConfig/BuildXCConfig.rb" "${SRCROOT}/.." "${SRCROOT}/tmp.xcconfig"
```
### Different environments ### Different environments
@ -150,7 +163,6 @@ $ env:ENVFILE=".env.staging"; react-native run-ios # powershell
This also works for `run-android`. Alternatively, there are platform-specific options below. This also works for `run-android`. Alternatively, there are platform-specific options below.
#### Android #### Android
The same environment variable can be used to assemble releases with a different config: The same environment variable can be used to assemble releases with a different config:
@ -171,6 +183,7 @@ project.ext.envConfigFiles = [
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle" apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
``` ```
<a name="ios-multi-scheme"></a>
#### iOS #### iOS

View File

@ -57,9 +57,11 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
3DF7F6AC203AA09B00D0EAB7 /* libReactNativeConfig-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libReactNativeConfig-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 3DF7F6AC203AA09B00D0EAB7 /* libReactNativeConfig-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libReactNativeConfig-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
50406729228CAD5A00E0438A /* ReadDotEnv.rb */ = {isa = PBXFileReference; lastKnownFileType = text.script.ruby; path = ReadDotEnv.rb; sourceTree = "<group>"; };
50830C45228DD3D000CEA8FC /* BuildXCConfig.rb */ = {isa = PBXFileReference; lastKnownFileType = text.script.ruby; path = BuildXCConfig.rb; sourceTree = "<group>"; };
EB2648DF1C7BE17A00B8F155 /* libReactNativeConfig.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libReactNativeConfig.a; sourceTree = BUILT_PRODUCTS_DIR; }; EB2648DF1C7BE17A00B8F155 /* libReactNativeConfig.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libReactNativeConfig.a; sourceTree = BUILT_PRODUCTS_DIR; };
EB2648E21C7BE17A00B8F155 /* ReactNativeConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReactNativeConfig.h; sourceTree = "<group>"; }; EB2648E21C7BE17A00B8F155 /* ReactNativeConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReactNativeConfig.h; sourceTree = "<group>"; };
EBE4E8291C7BF6DD000F8875 /* BuildDotenvConfig.ruby */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = BuildDotenvConfig.ruby; sourceTree = "<group>"; }; EBE4E8291C7BF6DD000F8875 /* BuildDotenvConfig.rb */ = {isa = PBXFileReference; lastKnownFileType = text.script.ruby; path = BuildDotenvConfig.rb; sourceTree = "<group>"; };
EBE4E8461C7D2456000F8875 /* ReactNativeConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReactNativeConfig.m; sourceTree = "<group>"; }; EBE4E8461C7D2456000F8875 /* ReactNativeConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReactNativeConfig.m; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@ -101,9 +103,11 @@
EB2648E11C7BE17A00B8F155 /* ReactNativeConfig */ = { EB2648E11C7BE17A00B8F155 /* ReactNativeConfig */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
50830C45228DD3D000CEA8FC /* BuildXCConfig.rb */,
EB2648E21C7BE17A00B8F155 /* ReactNativeConfig.h */, EB2648E21C7BE17A00B8F155 /* ReactNativeConfig.h */,
EBE4E8461C7D2456000F8875 /* ReactNativeConfig.m */, EBE4E8461C7D2456000F8875 /* ReactNativeConfig.m */,
EBE4E8291C7BF6DD000F8875 /* BuildDotenvConfig.ruby */, EBE4E8291C7BF6DD000F8875 /* BuildDotenvConfig.rb */,
50406729228CAD5A00E0438A /* ReadDotEnv.rb */,
); );
path = ReactNativeConfig; path = ReactNativeConfig;
sourceTree = "<group>"; sourceTree = "<group>";
@ -172,6 +176,7 @@
developmentRegion = English; developmentRegion = English;
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
knownRegions = ( knownRegions = (
English,
en, en,
); );
mainGroup = EB2648D61C7BE17A00B8F155; mainGroup = EB2648D61C7BE17A00B8F155;
@ -192,13 +197,13 @@
files = ( files = (
); );
inputPaths = ( inputPaths = (
"$(SRCROOT)/ReactNativeConfig/BuildDotenvConfig.ruby", "$(SRCROOT)/ReactNativeConfig/BuildDotenvConfig.rb",
); );
outputPaths = ( outputPaths = (
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = ./ReactNativeConfig/BuildDotenvConfig.ruby; shellScript = "./ReactNativeConfig/BuildDotenvConfig.rb\n";
}; };
EBE4E8281C7BF689000F8875 /* ShellScript */ = { EBE4E8281C7BF689000F8875 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
@ -206,13 +211,13 @@
files = ( files = (
); );
inputPaths = ( inputPaths = (
"$(SRCROOT)/ReactNativeConfig/BuildDotenvConfig.ruby", "$(SRCROOT)/ReactNativeConfig/BuildDotenvConfig.rb",
); );
outputPaths = ( outputPaths = (
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = ./ReactNativeConfig/BuildDotenvConfig.ruby; shellScript = "set -xe\necho \"$SRCROOT\"\n./ReactNativeConfig/BuildDotenvConfig.rb \"$SRCROOT/../../..\" \"$SRCROOT/ReactNativeConfig/\"\n";
}; };
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */

View File

@ -0,0 +1,30 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require_relative 'ReadDotEnv'
envs_root = ARGV[0]
m_output_path = ARGV[1]
puts "reading env file from #{envs_root} and writing .m to #{m_output_path}"
# Allow utf-8 charactor in config value
# For example, APP_NAME=中文字符
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8
dotenv, custom_env = read_dot_env(envs_root)
puts "read dotenv #{dotenv}"
# create obj file that sets DOT_ENV as a NSDictionary
dotenv_objc = dotenv.map { |k, v| %(@"#{k}":@"#{v.chomp}") }.join(',')
template = <<EOF
#define DOT_ENV @{ #{dotenv_objc} };
EOF
# write it so that ReactNativeConfig.m can return it
path = File.join(m_output_path, 'GeneratedDotEnv.m')
File.open(path, 'w') { |f| f.puts template }
File.delete('/tmp/envfile') if custom_env
puts "Wrote to #{path}"

View File

@ -1,79 +0,0 @@
#!/usr/bin/env ruby
# Allow utf-8 charactor in config value
# For example, APP_NAME=中文字符
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8
defaultEnvFile = ".env"
# pick a custom env file if set
if File.exists?("/tmp/envfile")
custom_env = true
file = File.read("/tmp/envfile").strip
else
custom_env = false
file = ENV["ENVFILE"] || defaultEnvFile
end
puts "Reading env from #{file}"
dotenv = begin
# https://regex101.com/r/cbm5Tp/1
dotenv_pattern = /^(?:export\s+|)(?<key>[[:alnum:]_]+)=((?<quote>["'])?(?<val>.*?[^\\])\k<quote>?|)$/
# find that above node_modules/react-native-config/ios/
path = File.join(Dir.pwd, "../../../#{file}")
if File.exists?(path)
raw = File.read(path)
elsif File.exists?(file)
raw = File.read(file)
else
defaultEnvPath = File.join(Dir.pwd, "../../../#{defaultEnvFile}")
if !File.exists?(defaultEnvPath)
# try as absolute path
defaultEnvPath = defaultEnvFile
end
defaultRaw = File.read(defaultEnvPath)
if (defaultRaw)
raw = defaultRaw + "\n" + raw
end
end
raw.split("\n").inject({}) do |h, line|
m = line.match(dotenv_pattern)
next h if m.nil?
key = m[:key]
# Ensure string (in case of empty value) and escape any quotes present in the value.
val = m[:val].to_s.gsub('"', '\"')
h.merge(key => val)
end
rescue Errno::ENOENT
puts("**************************")
puts("*** Missing .env file ****")
puts("**************************")
{} # set dotenv as an empty hash
end
# create obj file that sets DOT_ENV as a NSDictionary
dotenv_objc = dotenv.map { |k, v| %Q(@"#{k}":@"#{v.chomp}") }.join(",")
template = <<EOF
#define DOT_ENV @{ #{dotenv_objc} };
EOF
# write it so that ReactNativeConfig.m can return it
path = File.join(ENV["SYMROOT"], "GeneratedDotEnv.m")
File.open(path, "w") { |f| f.puts template }
# create header file with defines for the Info.plist preprocessor
info_plist_defines_objc = dotenv.map { |k, v| %Q(#define __RN_CONFIG_#{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 }
if custom_env
File.delete("/tmp/envfile")
end
puts "Wrote to #{path}"

View File

@ -0,0 +1,13 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require_relative 'ReadDotEnv'
envs_root = ARGV[0]
config_output = ARGV[1]
puts "reading env file from #{envs_root} and writing .config to #{config_output}"
dotenv, custom_env = read_dot_env(envs_root)
dotenv_xcconfig = dotenv.map { |k, v| %(#{k}=#{v}) }.join("\n")
File.open(config_output, 'w') { |f| f.puts dotenv_xcconfig }

View File

@ -0,0 +1,58 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
# Allow utf-8 charactor in config value
# For example, APP_NAME=中文字符
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8
# TODO: introduce a parameter which controls how to build relative path
def read_dot_env(envs_root)
defaultEnvFile = '.env'
puts "going to read env file from root folder #{envs_root}"
# pick a custom env file if set
if File.exist?('/tmp/envfile')
custom_env = true
file = 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:]_]+)=((?<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
end
defaultRaw = File.read(defaultEnvPath)
raw = defaultRaw + "\n" + raw if defaultRaw
end
raw.split("\n").inject({}) do |h, line|
m = line.match(dotenv_pattern)
next h if m.nil?
key = m[:key]
# Ensure string (in case of empty value) and escape any quotes present in the value.
val = m[:val].to_s.gsub('"', '\"')
h.merge(key => val)
end
rescue Errno::ENOENT
puts('**************************')
puts('*** Missing .env file ****')
puts('**************************')
return [{}, false] # set dotenv as an empty hash
end
[dotenv, custom_env]
end

View File

@ -1,23 +1,35 @@
require "json" # frozen_string_literal: true
package = JSON.parse(File.read(File.join(__dir__, "package.json"))) require 'json'
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = "react-native-config" s.name = 'react-native-config'
s.version = package["version"] s.version = package['version']
s.summary = "Expose config variables to React Native apps" s.summary = 'Expose config variables to React Native apps'
s.author = "Pedro Belo" s.author = 'Pedro Belo'
s.homepage = "https://github.com/luggit/react-native-config" s.homepage = 'https://github.com/luggit/react-native-config'
s.license = "MIT" s.license = 'MIT'
s.ios.deployment_target = "7.0" s.ios.deployment_target = '7.0'
s.tvos.deployment_target = "9.0" s.tvos.deployment_target = '9.0'
s.source = { :git => "https://github.com/luggit/react-native-config.git", :tag => "#{s.version}" } s.source = { git: 'https://github.com/luggit/react-native-config.git', tag: s.version.to_s }
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"
),
execution_position: :before_compile,
input_files: ['$(SRCROOT)/ReactNativeConfig/BuildDotenvConfig.rb']
}
s.source_files = "ios/**/*.{h,m}" s.source_files = 'ios/**/*.{h,m}'
s.requires_arc = true s.requires_arc = true
s.dependency "React" s.dependency 'React'
end end

Binary file not shown.

After

Width:  |  Height:  |  Size: 599 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 KiB