Skip to content

Commit

Permalink
Merge pull request #224 from TechnologyBrewery/217-uv-poetry-initiali…
Browse files Browse the repository at this point in the history
…zehabushumojo

[217] Support UV /Poetry switch for maven plugin - InitializeHabushuMojo
  • Loading branch information
jaebchoi authored Feb 14, 2025
2 parents 67b16d8 + c78be3d commit e320537
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 65 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,4 @@ Thumbs.db
# https://python-poetry.org/docs/configuration/#virtualenvsin-project
.venv
poetry.lock
uv.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.technologybrewery.habushu;

import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;

import java.io.File;

/**
* Abstract class that ensures pre-requisite tools that Habushu leverages are installed and available on the
* developer's machine to support the same functionality across multiple Mojo implementations.
*/
public abstract class AbstractInitializeHabushu {
/**
* Base directory from which to write Python package and dependency management files.
*/
protected File baseDir;

/**
* Logger from calling class to leverage.
*/
protected Log log;

/**
* Boolean to check whether to override package version
*/
protected boolean overridePackageVersion;

/**
* Expected PackageVersion from pom file.
*/
protected String expectedPythonPackageVersion;


/**
* New instance - these values are typically passed in from Maven-enabled parameters in the calling Mojo.
*
* @param baseDir base directory from which to operate for this module
* @param log the logger to use for output
* @param overridePackageVersion whether we override package version
* @param expectedPythonPackageVersion expected PackageVersion from pom file
*/
public AbstractInitializeHabushu(File baseDir, Log log, boolean overridePackageVersion, String expectedPythonPackageVersion) {
this.baseDir = baseDir;
this.log = log;
this.overridePackageVersion = overridePackageVersion;
this.expectedPythonPackageVersion = expectedPythonPackageVersion;
}

public abstract void doExecute() throws MojoExecutionException, MojoFailureException;


}
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package org.technologybrewery.habushu;

import java.util.Arrays;

import org.apache.commons.lang3.StringUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.technologybrewery.habushu.exec.PoetryCommandHelper;
import org.technologybrewery.habushu.util.HabushuUtil;

/**
* Ensures that the current project is a valid Poetry project and initializes
* Ensures that the current project is a valid Poetry or UV project and initializes
* Habushu versioning conventions, specifically aligning the version specified
* in the {@code pom.xml} with the version in the project's
* {@code pyproject.toml}.
Expand All @@ -20,37 +17,15 @@ public class InitializeHabushuMojo extends AbstractHabushuMojo {

@Override
public void doExecute() throws MojoExecutionException, MojoFailureException {

getLog().info("Validating Poetry-based project structure...");
PoetryCommandHelper poetryHelper = createPoetryCommandHelper();
try {
poetryHelper.execute(Arrays.asList("check"));
} catch (HabushuException e) {
getLog().debug("Failure encountered while running 'poetry check'!", e);
getLog().warn("poetry check failed (debug contains more details) - this is likely due to a "
+ "mismatch between your pyproject.toml and poetry.lock file - attempting to correct...");
poetryHelper.execute(poetryHelper.createLockCommand());
getLog().warn("Corrected - pyproject.toml and poetry.lock now synced");
}

String currentPythonPackageVersion = poetryHelper.execute(Arrays.asList("version", "-s"));
String pomVersion = project.getVersion();
String expectedPythonPackageVersion = getPythonPackageVersion(pomVersion, false, null);

if (!StringUtils.equals(currentPythonPackageVersion, expectedPythonPackageVersion)) {
if (overridePackageVersion) {
getLog().info(String.format("Setting Poetry package version to %s", expectedPythonPackageVersion));
getLog().info(
"If you do *not* want the Poetry package version to be automatically synced with the POM version, set <overridePackageVersion>false</overridePackageVersion> in the plugin's <configuration>");
poetryHelper.executeAndLogOutput(Arrays.asList("version", expectedPythonPackageVersion));
} else {
getLog().debug(String.format(
"Poetry package version set to %s in pyproject.toml does not align with expected POM-derived version of %s",
currentPythonPackageVersion, expectedPythonPackageVersion));
}

if (HabushuUtil.checkPythonPackageManager(getPyProjectTomlFile()) == HabushuUtil.PackageManager.POETRY){
var initializeHabushuPoetry = new InitializeHabushuPoetry(getPythonProjectBaseDir(), getLog(), overridePackageVersion, expectedPythonPackageVersion );
initializeHabushuPoetry.doExecute();
} else {
var initializeHabushuUV = new InitializeHabushuUV(getPythonProjectBaseDir(), getLog(), overridePackageVersion, expectedPythonPackageVersion);
initializeHabushuUV.doExecute();
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.technologybrewery.habushu;

import org.apache.commons.lang3.StringUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.technologybrewery.habushu.exec.PoetryCommandHelper;

import java.io.File;
import java.util.Arrays;

public class InitializeHabushuPoetry extends AbstractInitializeHabushu{

public InitializeHabushuPoetry( File baseDir, Log log, boolean overridePackageVersion, String expectedPythonPackageVersion) {
super(baseDir, log, overridePackageVersion, expectedPythonPackageVersion);
}


@Override
public void doExecute() throws MojoExecutionException, MojoFailureException {
log.info("Validating Poetry-based project structure...");
PoetryCommandHelper poetryHelper = new PoetryCommandHelper(baseDir);
try {
poetryHelper.execute(Arrays.asList("check"));
} catch (HabushuException e) {
log.debug("Failure encountered while running 'poetry check'!", e);
log.warn("poetry check failed (debug contains more details) - this is likely due to a "
+ "mismatch between your pyproject.toml and poetry.lock file - attempting to correct...");
poetryHelper.execute(poetryHelper.createLockCommand());
log.warn("Corrected - pyproject.toml and poetry.lock now synced");
}

String currentPythonPackageVersion = poetryHelper.execute(Arrays.asList("version", "-s"));

if (!StringUtils.equals(currentPythonPackageVersion, expectedPythonPackageVersion)) {
if (overridePackageVersion) {
log.info(String.format("Setting Poetry package version to %s", expectedPythonPackageVersion));
log.info(
"If you do *not* want the Poetry package version to be automatically synced with the POM version, set <overridePackageVersion>false</overridePackageVersion> in the plugin's <configuration>");
poetryHelper.executeAndLogOutput(Arrays.asList("version", expectedPythonPackageVersion));
} else {
log.debug(String.format(
"Poetry package version set to %s in pyproject.toml does not align with expected POM-derived version of %s",
currentPythonPackageVersion, expectedPythonPackageVersion));
}

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.technologybrewery.habushu;

import org.apache.commons.lang3.StringUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.technologybrewery.habushu.exec.UVCommandHelper;

import java.io.File;
import java.util.Arrays;

public class InitializeHabushuUV extends AbstractInitializeHabushu{

public InitializeHabushuUV( File baseDir, Log log, boolean overridePackageVersion, String expectedPythonPackageVersion) {
super(baseDir, log, overridePackageVersion, expectedPythonPackageVersion);
}

@Override
public void doExecute() throws MojoExecutionException {
log.info("Validating UV-based project structure...");
UVCommandHelper uvHelper = new UVCommandHelper(baseDir);
try {
uvHelper.execute(Arrays.asList("lock", "--check"));
} catch (HabushuException e) {
log.debug("Failure encountered while running 'uv lock --check'!", e);
log.warn("uv lock --check failed (debug contains more details) - this is likely due to a "
+ "mismatch between your pyproject.toml and uv.lock file or missing uv.lock file. - attempting to correct...");
uvHelper.execute(uvHelper.createLockCommand());
log.warn("Corrected - pyproject.toml and uv.lock now synced");
}

String currentPythonPackageVersion = uvHelper.executeUVTool(Arrays.asList("--from=toml-cli", "toml", "get", "--toml-path=pyproject.toml", "project.version"));
if (!StringUtils.equals(currentPythonPackageVersion, expectedPythonPackageVersion)) {
if (overridePackageVersion) {
log.info(String.format("Setting UV package version to %s", expectedPythonPackageVersion));
log.info(
"If you do *not* want the UV package version to be automatically synced with the POM version, set <overridePackageVersion>false</overridePackageVersion> in the plugin's <configuration>");
uvHelper.executeUVToolAndLogOutput(Arrays.asList("--from=toml-cli", "toml", "get", "--toml-path=pyproject.toml", "project.version", expectedPythonPackageVersion));
} else {
log.debug(String.format(
"UV package version set to %s in pyproject.toml does not align with expected POM-derived version of %s",
currentPythonPackageVersion, expectedPythonPackageVersion));
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public PoetryCommandHelper(File workingDirectory) {
* Poetry is not installed, the returned {@link String} part of the {@link Pair}
* will be {@code null}.
*
* @return
* @return Pair of installed and version
*/
public Pair<Boolean, String> getIsPoetryInstalledAndVersion() {
try {
Expand All @@ -61,22 +61,12 @@ public Pair<Boolean, String> getIsPoetryInstalledAndVersion() {
}
}

/**
* Returns a {@link String} indicating the relative path to the poetry
* cache directory. This is equivalent to {@code poetry config cache-dir}.
*
* @return
*/
public String getPoetryCacheDirectoryPath() throws MojoExecutionException {
return execute(Arrays.asList("config", "cache-dir"));
}

/**
* Returns whether the specified dependency package is installed within this
* Poetry project's virtual environment (and pyproject.toml).
*
* @param packageName
* @return
* @return whether Dependency is Installed
*/
public boolean isDependencyInstalled(String packageName) {
try {
Expand All @@ -94,7 +84,7 @@ public boolean isDependencyInstalled(String packageName) {
*
* @param packageName
*/
public void installDevelopmentDependency(String packageName) throws MojoExecutionException {
public void installDevelopmentDependency(String packageName) {
execute(Arrays.asList("add", packageName, "--group", "dev"));
}

Expand All @@ -105,11 +95,10 @@ public void installDevelopmentDependency(String packageName) throws MojoExecutio
* Poetry command, or it is desirable to not show the command's generated
* stdout.
*
* @param arguments
* @return
* @throws MojoExecutionException
* @param arguments list of arguments for poetry commands
* @return execution Result
*/
public String execute(List<String> arguments) throws MojoExecutionException {
public String execute(List<String> arguments) {
if (logger.isInfoEnabled()) {
logger.info("Executing Poetry command: {} {}", POETRY_COMMAND, StringUtils.join(arguments, " "));
}
Expand All @@ -124,11 +113,10 @@ public String execute(List<String> arguments) throws MojoExecutionException {
* immediately show all of the stdout/stderr produced by a Poetry command for
* diagnostic purposes.
*
* @param arguments
* @return
* @throws MojoExecutionException
* @param arguments list of arguments for poetry commands
* @return execution value
*/
public int executeAndLogOutput(List<String> arguments) throws MojoExecutionException {
public int executeAndLogOutput(List<String> arguments) {
if (logger.isInfoEnabled()) {
logger.info("Executing Poetry command: {} {}", POETRY_COMMAND, StringUtils.join(arguments, " "));
}
Expand All @@ -143,7 +131,7 @@ public int executeAndLogOutput(List<String> arguments) throws MojoExecutionExcep
* and it is desirable to immediately show all the stdout/stderr produced by a Poetry command for
* diagnostic purposes.
*
* @param arguments
* @param arguments list of arguments for poetry commands
* @param environmentVariables
*/
public void executeAndLogOutput(List<String> arguments, Map<String, String> environmentVariables) {
Expand All @@ -162,11 +150,9 @@ public void executeAndLogOutput(List<String> arguments, Map<String, String> envi
* as passwords.
*
* @param argAndIsSensitivePairs
* @return
* @throws MojoExecutionException
* @return execution value
*/
public int executeWithSensitiveArgsAndLogOutput(List<Pair<String, Boolean>> argAndIsSensitivePairs)
throws MojoExecutionException {
public int executeWithSensitiveArgsAndLogOutput(List<Pair<String, Boolean>> argAndIsSensitivePairs) {
if (logger.isInfoEnabled()) {
List<String> argsWithSensitiveArgsMasked = argAndIsSensitivePairs.stream()
.map(pair -> pair.getRight() ? "XXXX" : pair.getLeft()).collect(Collectors.toList());
Expand All @@ -188,10 +174,10 @@ public int executeWithSensitiveArgsAndLogOutput(List<Pair<String, Boolean>> argA
* when the timeout expires. After the timeout expires, this method will
* continue to wait until underlying Poetry command completes.
*
* @param arguments
* @param arguments list of arguments for poetry commands
* @param timeout
* @param timeUnit
* @return
* @return execution value
*/
public Integer executePoetryCommandAndLogAfterTimeout(List<String> arguments, int timeout, TimeUnit timeUnit) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Expand All @@ -218,10 +204,9 @@ public Integer executePoetryCommandAndLogAfterTimeout(List<String> arguments, in
* Installs a Poetry plugin with the given name.
*
* @param name
* @return
* @throws MojoExecutionException
* @return execution value
*/
public int installPoetryPlugin(String name) throws MojoExecutionException {
public int installPoetryPlugin(String name) {
List<String> args = new ArrayList<String>();
args.add("self");
args.add("add");
Expand All @@ -246,7 +231,7 @@ protected ProcessExecutor createPoetryExecutor(List<String> arguments, Map<Strin
/**
* Returns a {@link boolean} indicating whether the specified Poetry version is at least 2.0.0.
*
* @return
* @return boolean isPoetryVersionAtLeast2
*/
public boolean isPoetryVersionAtLeast2(){
String poetryVersion = getIsPoetryInstalledAndVersion().getRight();
Expand Down
Loading

0 comments on commit e320537

Please sign in to comment.