Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a shell script to set environment variables for a kit #995

Merged
merged 11 commits into from
Apr 6, 2020
4 changes: 4 additions & 0 deletions schemas/kits-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
"type": "string",
"description": "Architecture to target"
},
"environmentSetupScript": {
"type": "string",
"description": "The absolute path to a script that modifies the environment for the Kit"
},
"environmentVariables": {
"type": "object",
"patternProperties": {
Expand Down
75 changes: 74 additions & 1 deletion src/kit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ export interface Kit {
*/
visualStudioArchitecture?: string;

/**
* Filename of a shell script which sets environment variables for the kit
*/
environmentSetupScript?: string;

/**
* Path to a CMake toolchain file.
*/
Expand Down Expand Up @@ -580,6 +585,64 @@ async function collectDevBatVars(devbat: string, args: string[], major_version:
return vars;
}

/**
* Gets the environment variables set by a shell script.
* @param kit The kit to get the environment variables for
*/
export async function getShellScriptEnvironment(kit: Kit): Promise<Map<string, string>|undefined> {
console.assert(kit.environmentSetupScript);
const filename = Math.random().toString() + (process.platform == 'win32' ? '.bat' : '.sh');
const script_filename = `vs-cmt-${filename}`;
const environment_filename = script_filename + '.env';
const script_path = path.join(paths.tmpDir, script_filename);
const environment_path = path.join(paths.tmpDir, environment_filename); // path of temp file in which the script writes the env vars to

let script = '';
let run_command = '';
if (process.platform == 'win32') { // windows
script += `call "${kit.environmentSetupScript}"\r\n`; // call the user batch script
script += `set >> ${environment_path}`; // write env vars to temp file
run_command = `call ${script_path}`;
} else { // non-windows
script += `source "${kit.environmentSetupScript}"\n`; // run the user shell script
script +=`printenv >> ${environment_path}`; // write env vars to temp file
run_command = `/bin/bash -c "source ${script_path}"`; // run script in bash to enable bash-builtin commands like 'source'
}
try {
await fs.unlink(environment_path); // delete the temp file if it exists
} catch (error) {}
await fs.writeFile(script_path, script); // write batch file

const res = await proc.execute(run_command, [], null, {shell: true, silent: true}).result; // run script
await fs.unlink(script_path); // delete script file
const output = (res.stdout) ? res.stdout + (res.stderr || '') : res.stderr;

let env = '';
try {
/* When the script failed, envpath would not exist */
env = await fs.readFile(environment_path, {encoding: 'utf8'});
await fs.unlink(environment_path);
} catch (error) { log.error(error); }
if (!env || env === '') {
console.log(`Error running ${kit.environmentSetupScript} with:`, output);
return;
}

// split and trim env vars
const vars
= env.split('\n').map(l => l.trim()).filter(l => l.length !== 0).reduce<Map<string, string>>((acc, line) => {
const match = /(\w+)=?(.*)/.exec(line);
if (match) {
acc.set(match[1], match[2]);
} else {
log.error(localize('error.parsing.environment', 'Error parsing environment variable: {0}', line));
}
return acc;
}, new Map());
log.debug(localize('ok.running', 'OK running {0}, env vars: {1}', kit.environmentSetupScript, JSON.stringify([...vars])));
return vars;
}

/**
* Platform arguments for VS Generators
*/
Expand Down Expand Up @@ -755,13 +818,23 @@ export async function getVSKitEnvironment(kit: Kit): Promise<Map<string, string>
}

export async function effectiveKitEnvironment(kit: Kit, opts?: expand.ExpansionOptions): Promise<Map<string, string>> {
const host_env = objectPairs(process.env) as [string, string][];
let host_env;
const kit_env = objectPairs(kit.environmentVariables || {});
if (opts) {
for (const env_var of kit_env) {
env_var[1] = await expand.expandString(env_var[1], opts);
}
}
if (kit.environmentSetupScript) {
const shell_vars = await getShellScriptEnvironment(kit);
if (shell_vars) {
host_env = util.map(shell_vars, ([k, v]): [string, string] => [k.toLocaleUpperCase(), v]) as [string, string][];
}
}
if (host_env === undefined) {
// get host_env from process if it was not set by shell script before
host_env = objectPairs(process.env) as [string, string][];
}
if (kit.visualStudio && kit.visualStudioArchitecture) {
const vs_vars = await getVSKitEnvironment(kit);
if (vs_vars) {
Expand Down
29 changes: 28 additions & 1 deletion test/unit-tests/kitmanager.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {readKitsFile} from '@cmt/kit';
import {readKitsFile, getShellScriptEnvironment} from '@cmt/kit';
import {expect} from '@test/util';
import * as path from 'path';
import paths from '@cmt/paths';
import {fs} from '@cmt/pr';

// tslint:disable:no-unused-expression

Expand All @@ -23,4 +25,29 @@ suite('Kits test', async () => {
'VSCode Kit 2',
]);
});

test('Test load env vars from shell script', async() => {
const fname_extension = process.platform == 'win32' ? 'bat' : 'sh';
const fname = `cmake-kit-test-${Math.random().toString()}.${fname_extension}`;
const script_path = path.join(paths.tmpDir, fname);
// generate a file with test batch / shell script that sets two env vars
if (process.platform == 'win32') {
await fs.writeFile(script_path, `set "TESTVAR12=abc"\r\nset "TESTVAR13=cde"`);
} else {
await fs.writeFile(script_path, `export "TESTVAR12=abc"\nexport "TESTVAR13=cde"`);
}

const kit = { name: "Test Kit 1", environmentSetupScript: script_path };
const env_vars = await getShellScriptEnvironment(kit);
await fs.unlink(script_path);
expect(env_vars).to.not.be.undefined;

if (env_vars) {
const env_vars_arr = Array.from(env_vars);
// must contain all env vars, not only the ones we defined!
expect(env_vars_arr.length).to.be.greaterThan(2);
expect(env_vars_arr).to.deep.include(['TESTVAR12', 'abc']);
expect(env_vars_arr).to.deep.include(['TESTVAR13', 'cde']);
}
});
});