org.technologybrewery.habushu.exec.PyenvCommandHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of habushu-maven-plugin Show documentation
Show all versions of habushu-maven-plugin Show documentation
Leverages Poetry and Pyenv to provide an automated, predictable order of execution of build commands
that apply DevOps and configuration management best practices
package org.technologybrewery.habushu.exec;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.slf4j.event.Level;
import org.technologybrewery.habushu.HabushuException;
import org.technologybrewery.habushu.util.HabushuUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Facilitates the execution of pyenv commands related to managing, selecting,
* and installation desired Python versions.
*/
public class PyenvCommandHelper {
private static final String PYENV_COMMAND = "pyenv";
private static final Logger logger = LoggerFactory.getLogger(PyenvCommandHelper.class);
private File workingDirectory;
public PyenvCommandHelper(File workingDirectory) {
this.workingDirectory = workingDirectory;
}
/**
* Returns a boolean value indicating whether pyenv is installed.
*/
public boolean isPyenvInstalled() {
try {
String foundVersion = executeWithDebugLogging(Arrays.asList("--version"));
logger.debug("Found " + foundVersion);
} catch (Throwable e) {
return false;
}
return true;
}
/**
* Retrieves the version of Python that is set for the configured working
* directory.
*
* @return
*/
public String getCurrentPythonVersion() throws MojoExecutionException {
return executeWithDebugLogging(Arrays.asList("version-name"));
}
/**
* Updates Python processes launched at the configured working directory to use
* the specified version of Python by executing the following steps:
*
* - Checks pyenv for the currently installed Python versions
* - If the specified version of Python isn't installed, attempt to install it
* using "pyenv install {@literal
}"
* - If installing the target Python version using "pyenv install
* {@literal
}" fails, try to install the target version via "pyenv
* install --patch" through {@link #installPythonVersionViaPatch(String)}
* - Finally, set the locally used version of Python (relative to the
* configured working directory) to use the target version
*
* it.
*
* @param targetVersion desired version of Python to use
* @param patchInstallScript if installing the specified Python version via
* "pyenv install {@literal }" fails, path to
* the {@link File} that will be
*/
public void updatePythonVersion(String targetVersion, File patchInstallScript) throws MojoExecutionException {
List installedPythonVersions = getInstalledPythonVersions();
if (!installedPythonVersions.contains(targetVersion)) {
logger.info("Could not find Python version {} in following versions [{}] that are installed via pyenv. Installing version {} now...", targetVersion, StringUtils.join(installedPythonVersions, ", "), targetVersion);
installPythonVersion(targetVersion, patchInstallScript);
}
execute(Arrays.asList("local", targetVersion));
}
/**
* Retrieves a list of the locally installed versions of Python that are managed
* by pyenv.
*
* @return
*/
private List getInstalledPythonVersions() throws MojoExecutionException {
String versionsResult = execute(Arrays.asList("versions", "--bare"));
if (StringUtils.isNotEmpty(versionsResult)) {
return Arrays.asList(StringUtils.split(versionsResult));
} else {
return Collections.emptyList();
}
}
/**
* Installs Python using "pyenv install {@literal }". If this fails, we
* try to patch what may be the issue.
*
* @param targetVersion desired Python version to install
* @param patchInstallScript if the initially executed "pyenv install
* {@literal }" command fails, write a shell
* script to this file which will attempt to patch
* what may be the issue.
*/
private void installPythonVersion(String targetVersion, File patchInstallScript) {
try {
execute(Arrays.asList("install", targetVersion));
} catch (Throwable t) {
logger.warn("Could not install Python {} via normal install, attempting install with patch...", targetVersion);
installPythonVersionViaPatch(targetVersion, patchInstallScript);
}
}
/**
* Writes the necessary bash script commands to the given {@link File} to
* install Python through pyenv with a patch that attempts to fix a compilation
* error. After the bash script is written and its permissions appropriately
* updated, the script will be executed.
*
* @param pythonVersion desired Python version to install
* @param patchInstallScript target file to which the script will be written and
* executed from
*/
private void installPythonVersionViaPatch(String pythonVersion, File patchInstallScript) {
HabushuUtil.createFileAndGivePermissions(patchInstallScript);
StringBuilder commandList = new StringBuilder();
commandList.append("#!/bin/bash" + "\n");
commandList.append("pyenv install --patch ");
commandList.append(pythonVersion);
commandList.append(" < <(curl -sSL https://github.com/python/cpython/commit/8ea6353.patch\\?full_index\\=1) " + "\n");
HabushuUtil.writeLinesToFile(commandList.toString(), patchInstallScript.getAbsolutePath());
try {
HabushuUtil.runBashScript(patchInstallScript.getAbsolutePath());
} catch (Throwable t) {
throw new HabushuException(String.format("Failed to install Python version %s with patch", pythonVersion), t);
}
}
/**
* Executes a pyenv command with the given arguments, logs the executed command at the info level,
* and returns the resultant process output as a string.
*
* @param arguments
* @return
* @throws MojoExecutionException
*/
protected String execute(List arguments) throws MojoExecutionException {
return execute(arguments, Level.INFO);
}
/**
* Executes a pyenv command with the given arguments, logs the executed command at the debug level,
* and returns the resultant process output as a string.
*
* @param arguments
* @return
* @throws MojoExecutionException
*/
protected String executeWithDebugLogging(List arguments) throws MojoExecutionException {
return execute(arguments, Level.DEBUG);
}
private String execute(List arguments, Level logLevel) {
ProcessExecutor executor = createPyenvExecutor(arguments);
if (Level.DEBUG.equals(logLevel) && logger.isDebugEnabled()) {
logger.debug("Executing pyenv command: {} {}", PYENV_COMMAND, StringUtils.join(arguments, " "));
} else if (Level.INFO.equals(logLevel) && logger.isInfoEnabled()) {
logger.info("Executing pyenv command: {} {}", PYENV_COMMAND, StringUtils.join(arguments, " "));
}
return executor.executeAndGetResult(logger);
}
protected ProcessExecutor createPyenvExecutor(List arguments) {
List fullCommandArgs = new ArrayList<>();
fullCommandArgs.add(PYENV_COMMAND);
fullCommandArgs.addAll(arguments);
return new ProcessExecutor(workingDirectory, fullCommandArgs, Platform.guess(), null);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy