Skip to content

Commit

Permalink
#62 ✨ initial Baton integration to support automated migration for mo…
Browse files Browse the repository at this point in the history
…norepo dependencies; with PR feedback
  • Loading branch information
d-ryan-ashcraft committed Nov 15, 2023
1 parent c565122 commit 4e7a2d1
Show file tree
Hide file tree
Showing 15 changed files with 542 additions and 92 deletions.
5 changes: 5 additions & 0 deletions habushu-maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@
<artifactId>maven-deploy-plugin</artifactId>
<version>${version.maven.deploy.plugin}</version>
</dependency>
<dependency>
<groupId>org.technologybrewery.baton</groupId>
<artifactId>baton-maven-plugin</artifactId>
<version>0.1.0</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-sec-dispatcher</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.technologybrewery.habushu.exec.PoetryCommandHelper;
import org.technologybrewery.habushu.util.TomlReplacementTuple;
import org.technologybrewery.habushu.util.TomlUtils;

import java.io.BufferedReader;
import java.io.File;
Expand Down Expand Up @@ -286,7 +288,7 @@ private void executeDetailedManagedDependencyMismatchActions(Map<String, TomlRep
if (dependencyMap.containsKey(packageName)) {
Object packageRhs = dependencyMap.get(packageName);

if (representsLocalDevelopmentVersion(packageRhs)) {
if (TomlUtils.representsLocalDevelopmentVersion(packageRhs)) {
getLog().info(String.format("%s does not have a specific version to manage - skipping", packageName));
getLog().debug(String.format("\t %s", packageRhs.toString()));
continue;
Expand Down Expand Up @@ -373,8 +375,8 @@ protected void performPendingDependencyReplacements(Map<String, TomlReplacementT

TomlReplacementTuple matchedTuple = replacements.get(key);
if (matchedTuple != null) {
String original = escapeTomlRightHandSide(matchedTuple.getOriginalOperatorAndVersion());
String updated = escapeTomlRightHandSide(matchedTuple.getUpdatedOperatorAndVersion());
String original = TomlUtils.escapeTomlRightHandSide(matchedTuple.getOriginalOperatorAndVersion());
String updated = TomlUtils.escapeTomlRightHandSide(matchedTuple.getUpdatedOperatorAndVersion());

if (line.endsWith(original)) {
line = line.replace(original, updated);
Expand All @@ -394,43 +396,15 @@ protected void performPendingDependencyReplacements(Map<String, TomlReplacementT
throw new HabushuException("Problem reading pyproject.toml to update with managed dependencies!", e);
}

writeTomlFile(pyProjectTomlFile, fileContent);
}
}
}

/**
* Handles escaping with double quotes only if the value is not an inline table.
*
* @param valueToEscape value to potentially escape
* @return value ready to write to toml file
*/
protected static String escapeTomlRightHandSide(String valueToEscape) {
return (!valueToEscape.contains("{")) ? DOUBLE_QUOTE + valueToEscape + DOUBLE_QUOTE : valueToEscape;
}

private static void writeTomlFile(File pyProjectTomlFile, String fileContent) {
if (fileContent != null) {
try (Writer writer = new FileWriter(pyProjectTomlFile)) {
writer.write(fileContent);
} catch (IOException e) {
throw new HabushuException("Problem writing pyproject.toml with managed dependency updates!", e);
}
}
}
try {
TomlUtils.writeTomlFile(pyProjectTomlFile, fileContent);

protected boolean representsLocalDevelopmentVersion(Object rawData) {
boolean localDevelopmentVersion = false;
} catch (IOException e) {
throw new HabushuException("Problem writing pyproject.toml with managed dependency updates!", e);
}

if (rawData instanceof CommentedConfig) {
CommentedConfig config = (CommentedConfig) rawData;
if (!config.contains("version")) {
localDevelopmentVersion = true;
}

}

return localDevelopmentVersion;
}

protected String getOperatorAndVersion(Object rawData) {
Expand All @@ -439,7 +413,7 @@ protected String getOperatorAndVersion(Object rawData) {
operatorAndVersion = (String) rawData;

} else if (rawData instanceof CommentedConfig) {
operatorAndVersion = convertCommentedConfigToToml((CommentedConfig) rawData);
operatorAndVersion = TomlUtils.convertCommentedConfigToToml((CommentedConfig) rawData);

} else {
getLog().warn(String.format("Could not process type %s - attempting to use toString() value!", rawData.getClass()));
Expand All @@ -450,31 +424,6 @@ protected String getOperatorAndVersion(Object rawData) {

}

protected static String convertCommentedConfigToToml(CommentedConfig config) {
StringBuilder sb = new StringBuilder();
sb.append("{");

sb.append("version = \"").append(config.get("version").toString()).append("\"");
List<String> extras = config.get("extras");
if (CollectionUtils.isNotEmpty(extras)) {
sb.append(", extras = [");
// NB: if we expect more complex values, such as multiple extras, more work would need to be done for
// both consistent formatting and comparison of these values. However, at the time of initially writing
// this method, there isn't a clear demand signal, so we are going to KISS for now:

for (int i = 0; i < extras.size(); i++) {
if (i > 0) {
sb.append(", ");
}
sb.append("\"").append(extras.get(i)).append("\"");
}
sb.append("]");
}
sb.append("}");

return sb.toString();
}

protected static String replaceSnapshotWithWildcard(String pomVersion) {
return pomVersion.substring(0, pomVersion.indexOf(SNAPSHOT)) + ".*";
}
Expand All @@ -492,31 +441,4 @@ protected static String replaceSnapshotWithDev(String pomVersion) {
return pomVersion.substring(0, pomVersion.indexOf(SNAPSHOT)) + ".dev";
}

private class TomlReplacementTuple {
private String packageName;

private String originalOperatorAndVersion;

private String updatedOperatorAndVersion;

public TomlReplacementTuple(String packageName, String originalOperatorAndVersion, String updatedOperatorAndVersion) {
this.packageName = packageName;
this.originalOperatorAndVersion = originalOperatorAndVersion;
this.updatedOperatorAndVersion = updatedOperatorAndVersion;

}

public String getPackageName() {
return packageName;
}

public String getOriginalOperatorAndVersion() {
return originalOperatorAndVersion;
}

public String getUpdatedOperatorAndVersion() {
return updatedOperatorAndVersion;
}
}

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

import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.technologybrewery.baton.BatonMojo;

/**
* Overriding the baton-maven-plugin so we can get access to Habushu's classpath for migration configuration.
*/
@Mojo(name = "baton-migrate", defaultPhase = LifecyclePhase.VALIDATE, requiresDependencyResolution = ResolutionScope.COMPILE, threadSafe = true)
public class MigrateMojo extends BatonMojo {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package org.technologybrewery.habushu.migration;

import com.electronwill.nightconfig.core.CommentedConfig;
import com.electronwill.nightconfig.core.Config;
import com.electronwill.nightconfig.core.file.FileConfig;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.technologybrewery.baton.AbstractMigration;
import org.technologybrewery.baton.BatonException;
import org.technologybrewery.habushu.HabushuException;
import org.technologybrewery.habushu.util.TomlReplacementTuple;
import org.technologybrewery.habushu.util.TomlUtils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
* Automatically migrates any monorepo dependencies (e.g., foo = {path = "../foo", develop = true}) in the main
* [tool.poetry.dependencies] group into the [tool.poetry.group.monorepo.dependencies] group instead. As noted in the
* project's README.md, this prevents these dependencies from causing issues when Poetry projects are exported in
* development releases.
*/
public class CustomMonorepoGroupMigration extends AbstractMigration {

public static final Logger logger = LoggerFactory.getLogger(CustomMonorepoGroupMigration.class);

protected Map<String, TomlReplacementTuple> replacements = new HashMap<>();

protected boolean hasExistingMonoRepoDependenciesGroup;

@Override
protected boolean shouldExecuteOnFile(File file) {
replacements.clear();
hasExistingMonoRepoDependenciesGroup = false;

boolean shouldExecute = false;
try (FileConfig tomlFileConfig = FileConfig.of(file)) {
tomlFileConfig.load();

Optional<Config> toolPoetryDependencies = tomlFileConfig.getOptional(TomlUtils.TOOL_POETRY_DEPENDENCIES);
if (toolPoetryDependencies.isPresent()) {
Config foundDependencies = toolPoetryDependencies.get();
Map<String, Object> dependencyMap = foundDependencies.valueMap();

for (Map.Entry<String, Object> dependency : dependencyMap.entrySet()) {
String packageName = dependency.getKey();
Object packageRhs = dependency.getValue();
if (TomlUtils.representsLocalDevelopmentVersion(packageRhs)) {
String packageRshAsString = TomlUtils.convertCommentedConfigToToml((CommentedConfig) packageRhs);
logger.info("Found local dependency not within monorepo group! ({} = {})", packageName, packageRshAsString);
TomlReplacementTuple replacementTuple = new TomlReplacementTuple(packageName, packageRshAsString, "");
replacements.put(packageName, replacementTuple);
shouldExecute = true;
}
}
}

Optional<Config> toolPoetryMonorepoDependencies = tomlFileConfig.getOptional(TomlUtils.TOOL_POETRY_GROUP_MONOREPO_DEPENDENCIES);
if (toolPoetryMonorepoDependencies.isPresent()) {
hasExistingMonoRepoDependenciesGroup = true;
}
}

return shouldExecute;
}

@Override
protected boolean performMigration(File pyProjectTomlFile) {
String fileContent = StringUtils.EMPTY;
try (BufferedReader reader = new BufferedReader(new FileReader(pyProjectTomlFile))) {
String line = reader.readLine();
boolean injectAfterNextEmptyLine = false;

while (line != null) {
boolean addLine = true;
boolean isEmptyLine = line.isBlank();

if (line.contains(StringUtils.SPACE) && line.contains(TomlUtils.EQUALS)) {
String key = line.substring(0, line.indexOf(StringUtils.SPACE));

if (key == null) {
key = line.substring(0, line.indexOf(TomlUtils.EQUALS));
}

if (key != null) {
key = key.strip();

TomlReplacementTuple matchedTuple = replacements.get(key);
if (matchedTuple != null) {
// skip this line, we will add it back to [tool.poetry.group.monorepo.dependencies] later
addLine = false;
}
}

} else if (line.contains("[") && line.contains("]")) {
String key = line.strip();

if (hasExistingMonoRepoDependenciesGroup && (key.equals("[" + TomlUtils.TOOL_POETRY_GROUP_MONOREPO_DEPENDENCIES + "]"))) {
// skip this line as we are overriding with the line plus monorepo dependencies here:
addLine = false;
fileContent += line + "\n";
fileContent = injectMonorepoDependencies(fileContent);
} else if (!hasExistingMonoRepoDependenciesGroup && (key.equals("[" + TomlUtils.TOOL_POETRY_DEPENDENCIES + "]"))) {
injectAfterNextEmptyLine = true;
}
}

if (isEmptyLine && injectAfterNextEmptyLine) {
fileContent += "\n[" + TomlUtils.TOOL_POETRY_GROUP_MONOREPO_DEPENDENCIES + "]" + "\n";
fileContent = injectMonorepoDependencies(fileContent);
injectAfterNextEmptyLine = false;
}

if (addLine) {
fileContent += line + "\n";
}

line = reader.readLine();
}

} catch (IOException e) {
throw new HabushuException("Problem reading pyproject.toml to update with managed dependencies!", e);
}

try {
TomlUtils.writeTomlFile(pyProjectTomlFile, fileContent);
} catch (IOException e) {
throw new BatonException("Problem moving monorepo dependencies to [tool.poetry.group.monorepo.dependencies]!", e);
}

return true;

}

private String injectMonorepoDependencies(String fileContent) {
for (Map.Entry<String, TomlReplacementTuple> entry : replacements.entrySet()) {
fileContent += entry.getKey() + " = " + TomlUtils.escapeTomlRightHandSide(entry.getValue().getOriginalOperatorAndVersion()) + "\n";
}
return fileContent;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.technologybrewery.habushu.util;

public class TomlReplacementTuple {
private String packageName;

private String originalOperatorAndVersion;

private String updatedOperatorAndVersion;

public TomlReplacementTuple(String packageName, String originalOperatorAndVersion, String updatedOperatorAndVersion) {
this.packageName = packageName;
this.originalOperatorAndVersion = originalOperatorAndVersion;
this.updatedOperatorAndVersion = updatedOperatorAndVersion;

}

public String getPackageName() {
return packageName;
}

public String getOriginalOperatorAndVersion() {
return originalOperatorAndVersion;
}

public String getUpdatedOperatorAndVersion() {
return updatedOperatorAndVersion;
}
}
Loading

0 comments on commit 4e7a2d1

Please sign in to comment.