breandan
breandan

Reputation: 2024

Make environment variables accessible to Gradle task

I need to run a Gradle task inside a shell environment, which must be created before the task is launched. Using commandLine or executable is not appropriate, as I need to run the task in the same process as the shell script. Originally, I called the script directly inside gradlew, but later I decided to source it from build.gradle.kts and call subsequent tasks through gradlew:

val setupRosEnv by tasks.creating(Exec::class) {
    executable = "bash"

    args("-c", "source $rosPath/setup.sh && source gradlew myTask")
}

I can build everything by running ./gradlew setupRosEnv from the CLI. Besides sourcing the script then running gradlew, is there a way to achieve this using the Gradle API? The current solution seems a bit hacky, and is clunky for other tasks to depend on setupRosEnv, as this will lead to an infinite loop or must be explicitly handled to prevent tasks being run more than once.

As the shell script itself is generated by ROS, it cannot be translated to Gradle or easily parsed.

Upvotes: 9

Views: 5674

Answers (1)

alijandro
alijandro

Reputation: 12147

It depends how your gradle task myTask use the environment. If it use the environment by System.getenv, you can do use the following step.

  1. parse the bash environment file env.sh and load all variables into Properties
  2. append the environment variables to current process in java.lang.ProcessEnvironment by reflection
  3. use the injected environment variables in your build task

Below is just roughly an example code copied from Java with minor modification, but it works fine in gradle build task.

task myEnvironInjected << {
    println("task with injected enviroment")
}

task myBuildTask(dependsOn: myEnvironInjected) << {
    def v1 = System.getenv("V1")
    println("my build task running V1=${v1}")
}

myEnvironInjected.doFirst {
    final Map<String, String> bashEnvMap = new HashMap<>();

    try {
        // a simple simulation of bash command source env.sh
        // use it carefully
        BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream
                ("/path/to/env.sh")));

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream));

        String line;
        while ((line = reader.readLine()) != null) {
            if (line.length() > 0 && line.startsWith("export ")) {
                String newLine = line.trim().replaceFirst("^export ", "");
                // remove single or double quote from the value if it has
                int quoteIndex = newLine.indexOf('=')+1;
                if (quoteIndex < newLine.length() && (newLine.charAt(quoteIndex) == ('"' as char) ||
                        newLine.charAt(quoteIndex) == ('\'' as char))) {
                    newLine = newLine.substring(0, quoteIndex) + newLine.substring(quoteIndex+1, newLine.length()-1);
                }
                writer.write(newLine);
                writer.newLine();
            }
        }
        writer.flush();
        writer.close();

        InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
        Properties properties = new Properties();
        properties.load(inputStream);

        for (Map.Entry<Object, Object> entry : properties.entrySet()) {
            bashEnvMap.put(((String) entry.getKey()), ((String) entry.getValue()));
        }

        outputStream.close();
        inputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }

    for (Class<?> aClass : Collections.class.getDeclaredClasses()) {
        if ("java.util.Collections\$UnmodifiableMap".equals(aClass.getName())) {
            try {
                Field mapField = aClass.getDeclaredField("m");
                mapField.setAccessible(true);
                Object mapObject = mapField.get(System.getenv());
                Map<String, String> environMap = ((Map<String, String>) mapObject);
                environMap.putAll(bashEnvMap);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    for (Map.Entry<String, String> envEntry : System.getenv().entrySet()) {
        System.out.println(envEntry.getKey() + "=" + envEntry.getValue());
    }
}

My test file env.sh looks like this.

export V1="v1 vvv"
export V2='v 2222'
export V3=v33333

If your build task use the environment variables not by System.getenv, your hacking method might be the best solution.

task myBuildTaskWithSourceEnv(type: Exec) {
    commandLine '/bin/bash'
    setArgs(['-c', 'source ../env.sh;set;../gradlew :app:assembleDebug'])
}

Upvotes: 5

Related Questions