Using environment variables / parameterizing config.xml

11,998

Solution 1

As you mentioned in the post, there is not much documentation about this in the official cordova documentation. After spending some time on this analysis, this is what I concluded:

There is some minimal help available ready-made to parameterize plugin variables available in config.xml. This can be achieved through preference variables as mentioned in the official cordova link. But the problem with this approach is that its working depends on how on the plugin is coded.

I tried this approach with the facebook plugin, but it didn't work :( I tried as below:

<preference name="MY_CUSTOM_STRING" default="12345678901234567" />
    <plugin name="cordova-plugin-facebook4" spec="~1.7.1">
        <variable name="APP_ID">$MY_CUSTOM_STRING</variable>
        <variable name="APP_NAME" value="My_Appy_App"/>
    </plugin>

Tried out the same approach for google maps plugin and it worked :) I tried as below:

<preference name="MY_CUSTOM_STRING" default="12345678901234567" />
<plugin name="cordova-plugin-googlemaps" spec="~1.3.9">
    <variable name="API_KEY_FOR_ANDROID">$MY_CUSTOM_STRING</variable>
</plugin>

So all I could conclude is that the parameterizing approach is dependent on the core plugin code.

In the case of the facebook plugin, if you want to parameterize the APP_ID variable, then I guess hooks are the way to proceed. Even a simple windows batch file to replace a string match should be fine and it can be invoked on pre build action to achieve what you require. Hope it helps.

UPDATE:

I do agree with Brandon's comments.

With the limited time i had, I was able to come up with the cordova hook that resolves this issue. It may be a crude way and it can be refined too but for now this approach works fine. Have posted the hook as a sample app in my github page and the README file has complete info about it. Hope it helps. Keep me posted.

Solution 2

I've achieved that creating a template config.xml (the file is config.tpl.xml) and a before_prepare cordova hook to replace the variables in the template with the correct values and save the generated content in config.xml.

The cordova hook uses the npm package es6-template-strings:

npm install es6-template-strings --save-dev

The hook is:

#!/usr/bin/env node
var fs = require('fs');
var path = require('path');
var compile = require('es6-template-strings/compile');
var resolveToString = require('es6-template-strings/resolve-to-string');

var ROOT_DIR = process.argv[2];
var FILES = {
    SRC: "config.tpl.xml",
    DEST: "config.xml"
};

var env = process.env.NODE_ENV || 'dev';
var envFile = 'src/environments/environment.' + env + '.json';

var srcFileFull = path.join(ROOT_DIR, FILES.SRC);
var destFileFull = path.join(ROOT_DIR, FILES.DEST);
var configFileFull = path.join(ROOT_DIR, envFile);

var templateData = fs.readFileSync(srcFileFull, 'utf8');

var configData = fs.readFileSync(configFileFull, 'utf8');
var config = JSON.parse(configData);

var compiled = compile(templateData);
var content = resolveToString(compiled, config);

fs.writeFileSync(destFileFull, content);

I have files in the src/environments/ directory for different environments, that are chosen based on the NODE_ENV value that is defined when I build cordova. For example, if NODE_ENV=prod (production), then it would use the file environment.prod.json:

{
    ...
    "FACEBOOK_APP_ID": "11111111",
    "FACEBOOK_APP_NAME": "My Facebook App Name",
    ...
    "PUSH_SENDER_ID": "22222222",
    ...
}

When the hook is executed, this part in the cordova.tpl.xml:

<plugin name="cordova-plugin-facebook4" spec="~1.7.4">
    <variable name="APP_ID" value="${FACEBOOK_APP_ID}" />
    <variable name="APP_NAME" value="${FACEBOOK_APP_NAME}" />
</plugin>
<plugin name="phonegap-plugin-push" spec="~1.9.2">
    <variable name="SENDER_ID" value="${PUSH_SENDER_ID}" />
</plugin>

becomes like:

<plugin name="cordova-plugin-facebook4" spec="~1.7.4">
    <variable name="APP_ID" value="11111111" />
    <variable name="APP_NAME" value="My Facebook App Name" />
</plugin>
<plugin name="phonegap-plugin-push" spec="~1.9.2">
    <variable name="SENDER_ID" value="22222222" />
</plugin>

Just have in mind that you need to add the automatic cordova changes to config.xml to the template (like adding a plugin), but that is much better (and, in my case, less frequent) than having to change the variables before each build with different environments and much less error prone, although it is not ideal.

Update (2017-10-13)

Now when I add/remove plugins, the xml template file is also updated. I've just added the hooks described here.

Solution 3

@Ghandi's solution using cordova hooks is a good example of how this can be accomplished, though it's specifically windows only since it utilizes a batch file and PowerShell for the hook script.

As a cross-platform solution, you can utilize some sort of build tool on top of cordova. For the project I'm working on, we have a cordova project in a subdirectory and were already using gulp to build the rest of our app so we modified our task that copies our cordova assets to a build directory to make it also perform a search and replace on config.xml. It pulls replacements from environment variables and uses a dotenv library to load environment from a .env file (which is not checked into the repository).

var gulp = require('gulp'),
  replace = require('gulp-replace'),
  gutil = require('gulp-util'),
  filter = require('gulp-filter'),
  path = require('path'),
  dotenv = require('dotenv'),

  argv = require('yargs').argv,
  isRelease = !!(typeof argv.release !== 'undefined' ? argv.release : (typeof argv.r !== 'undefined' ? argv.r : false));

gulp.task('cordova_assets', ['clean', 'templates'], function() {
  dotenv.config({path: path.join(__dirname, 'cordova', '.env'), silent: true});
  var f = filter(['cordova/config.xml'], {restore: true});

  return gulp.src(['cordova/**/*'], {base: './'})
    .pipe(f)
    .pipe(replace(/\$\$([A-Z0-9_]+)/gi, function(substr, varname) {
      var repl = '';
      if(!isRelease && typeof process.env[varname + '_DEBUG'] !== 'undefined')
        repl = process.env[varname + '_DEBUG'];
      else if(isRelease && typeof process.env[varname + '_RELEASE'] !== 'undefined')
        repl = process.env[varname + '_RELEASE'];
      else if(typeof process.env[varname] !== 'undefined')
        repl = process.env[varname];
      else {
        throw new gutil.PluginError('cordova_config', {
          message: 'expected but could not find the environment variables "' + varname +
          '" or "' + varname + '_' + (isRelease ? 'RELEASE' : 'DEBUG') + '" which is used in cordova/config.xml. ' +
          'Add it to cordova/.env or specify them explicitly when running the build command.'
        });
      }

      console.log('replacing "%s" with "%s"', substr, repl);
      return repl;
    }))
    .pipe(f.restore)
    .pipe(gulp.dest('build'));
});

It will replace any variables in config.xml that begin with two dollar signs and consist of alphanumeric characters and underscores with the value of the corresponding environment variable if it exists (and throws an error if it doesn't). Additionally, you can suffix the variable within config.xml with either _DEBUG or _RELEASE and it'll use those values instead as appropriate.

It assumes the following project structure:

  • /
    • cordova
      • config.xml
      • ... (the rest of your cordova install)
      • .env (contains your environment variables)
    • build (the build directory and final output of the cordova app)
Share:
11,998
Explosion Pills
Author by

Explosion Pills

Deep in the heart of the lush forest of Gainesville, Florida -- pride of the sunshine. Software developer / director for Mobiquity, the greatest mobile development company ever conceived.

Updated on June 14, 2022

Comments

  • Explosion Pills
    Explosion Pills almost 2 years

    I am building an ionic/cordova project using the cordova-plugin-facebook4 plugin for Facebook authentication access. In config.xml this looks like:

    <plugin name="cordova-plugin-facebook4" spec="~1.7.1">
      <variable name="APP_ID" value="1234567890123456"/>
      <variable name="APP_NAME" value="My_Appy_App"/>
    </plugin>
    

    This works okay, but the APP_ID we are using is for the dev app and we have a separate facebook app for other environments such as QA.

    Is there any way to parameterize these variables in config.xml and have them be replaced as part of a build step? Something like:

    <plugin name="foo" spec="~0.0.0">
      <variable name="bar" value="${env.APP_ID}"/>
    </plugin>
    

    ... and then run APP_ID=baz ionic build android or something like that.

    I don't see anything in the cordova documentation that allows you to do this.

  • Explosion Pills
    Explosion Pills almost 8 years
    Can you explain how to use hooks in this case?
  • Brandon
    Brandon almost 8 years
    I believe one of the issues with the <preference> approach is that it still requires the variables to be defined in config.xml, just in a different place than the the <plugin>'s <variable>. The question is asking for an approach which allows the values for these variables to be provided at build time and not in a (likely) version-controlled file such as config.xml.
  • Gandhi
    Gandhi almost 8 years
    @Brandon Thanks for the comments. Have posted the update answer. Hope it helps.
  • Gandhi
    Gandhi almost 8 years
    @ExplosionPills Have posted the working hook for you. Thanks for giving me the opportunity to try out my first cordova hook :)
  • Gandhi
    Gandhi almost 8 years
    Thanks for posting the other way too to accomplish this. Useful info. Happy coding. Cheers