/* * Copyright 2009 Ludovic Claude. * Copyright 2011 Damien Raude-Morvan. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.debian.maven.packager; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.stream.XMLStreamException; import org.debian.maven.packager.interaction.ChoiceQuestion; import org.debian.maven.packager.interaction.SimpleQuestion; import org.debian.maven.packager.interaction.YesNoQuestion; import org.debian.maven.packager.util.*; import org.debian.maven.repo.Dependency; import org.debian.maven.repo.DependencyNotFoundException; import org.debian.maven.repo.DependencyRule; import org.debian.maven.repo.DependencyRuleSet; import org.debian.maven.repo.DependencyRuleSetFiles; import org.debian.maven.repo.ListOfPOMs; import org.debian.maven.repo.POMHandler; import org.debian.maven.repo.POMInfo; import org.debian.maven.repo.POMOptions; import org.debian.maven.repo.Substvars; import org.debian.maven.repo.DependencyRuleSetFiles.RulesType; import org.debian.maven.repo.POMInfo.DependencyType; import org.debian.maven.repo.POMTransformer; import org.debian.maven.repo.Repository; import org.debian.maven.repo.Rule; import static org.debian.maven.packager.DebianDependencies.Type.*; import static org.debian.maven.repo.DependencyRuleSetFiles.RulesType.*; /** * Analyze the Maven dependencies and extract the Maven rules to use * as well as the list of dependent packages. * * @author Ludovic Claude */ public class DependenciesSolver { private static final Logger log = Logger.getLogger(DependenciesSolver.class.getName()); private final IgnoreDependencyQuestions ignoreDependencyQuestion; private File baseDir; final POMTransformer pomTransformer = new POMTransformer(); private final File outputDirectory; String packageName; String packageType; private String packageVersion; File mavenRepo = new File("/usr/share/maven-repo"); // explore (search) for additional pom files or look only for those defined in debian/*.poms? boolean exploreProjects; private Repository repository; List<String> issues = new ArrayList<String>(); private List<Dependency> projectPoms = new ArrayList<Dependency>(); private List<ToResolve> toResolve = new ArrayList<ToResolve>(); private Set<Dependency> knownProjectDependencies = new TreeSet<Dependency>(); private Set<Dependency> ignoredDependencies = new TreeSet<Dependency>(); private DebianDependencies debianDeps = new DebianDependencies(); boolean runTests; boolean generateJavadoc; final boolean interactive; private boolean askedToFilterModules = false; private boolean filterModules = false; boolean verbose = false; private Map<String, POMInfo> pomInfoCache = new HashMap<String, POMInfo>(); /** The original POMs for reference */ private Map<String, POMInfo> originalPomInfoCache = new HashMap<String, POMInfo>(); /** Map of the previously selected rule for a given version */ private Map<String, Rule> versionToRules = new HashMap<String, Rule>(); /** List of packages and dependencies */ private Map<DebianDependency, Dependency> versionedPackagesAndDependencies = new HashMap<DebianDependency, Dependency>(); private List<Rule> defaultRules = new ArrayList<Rule>(); private PackageScanner scanner; public DependenciesSolver(File outputDirectory, PackageScanner scanner, boolean interactive) { this.outputDirectory = outputDirectory; this.scanner = scanner; this.interactive = interactive; this.ignoreDependencyQuestion = new IgnoreDependencyQuestions(interactive); pomTransformer.setVerbose(true); pomTransformer.setFixVersions(false); pomTransformer.setRulesFiles(initDependencyRuleSetFiles(outputDirectory, verbose)); defaultRules.add(new Rule("s/.*/debian/", "Change the version to the symbolic 'debian' version")); defaultRules.add(new Rule("*", "Keep the version")); defaultRules.add(new Rule("CUSTOM", "Custom rule")); } public static DependencyRuleSetFiles initDependencyRuleSetFiles(File outputDirectory, boolean verbose) { DependencyRuleSetFiles depFiles = new DependencyRuleSetFiles(); for (RulesType type : RulesType.values()) { if (type.filename != null) { File rulesFile = new File(outputDirectory, type.filename); String description = readResource(type.descriptionResource); DependencyRuleSet ruleSet = DependencyRuleSet.readRules(rulesFile, description, verbose, false); depFiles.get(type).addAll(ruleSet); } } return depFiles; } // TODO move to another class for reuse private static String readResource(String resource) { StringBuilder sb = new StringBuilder(); try { InputStream is = DependenciesSolver.class.getResourceAsStream("/" + resource); BufferedReader r = new BufferedReader(new InputStreamReader(is)); String line; while ((line = r.readLine()) != null) { sb.append(line); sb.append("\n"); } r.close(); } catch (IOException e) { log.log(Level.SEVERE, "Cannot read resource " + resource, e); } return sb.toString(); } private boolean isDefaultMavenPlugin(Dependency dependency) { if (getRepository() != null && getRepository().getSuperPOM() != null) { for (Dependency defaultPlugin : getRepository().getSuperPOM().getDependencies().get(DependencyType.PLUGIN_MANAGEMENT)) { if (defaultPlugin.equalsIgnoreVersion(dependency)) { return true; } } } return false; } private class ToResolve { private final File sourcePom; private final DependencyType listType; private final boolean buildTime; private final boolean mavenExtension; private final boolean management; private ToResolve(File sourcePom, DependencyType listType, boolean buildTime, boolean mavenExtension, boolean management) { this.sourcePom = sourcePom; this.listType = listType; this.buildTime = buildTime; this.mavenExtension = mavenExtension; this.management = management; } public void resolve() { try { POMInfo pom = getPOM(sourcePom); List<Dependency> dependenciesByType = pom.getDependencies().get(listType); for (Dependency dependency : dependenciesByType) { resolveDependency(dependency, sourcePom, buildTime, mavenExtension, management, false); } } catch (DependencyNotFoundException e) { log.log(Level.SEVERE, "Cannot resolve dependencies in " + sourcePom + ": " + e.getMessage()); } catch (Exception e) { log.log(Level.SEVERE, "Cannot resolve dependencies in " + sourcePom + ": " + e.getMessage(), e); } } } public void saveSubstvars() { Properties depVars = Substvars.loadSubstvars(outputDirectory, packageName); if (generateJavadoc) { System.out.println("Checking dependencies for documentation packages..."); debianDeps.add(DOC_RUNTIME, new DebianDependency("default-jdk-doc")); debianDeps.add(DOC_RUNTIME, scanner.addDocDependencies(debianDeps.get(RUNTIME), versionedPackagesAndDependencies)); debianDeps.add(DOC_OPTIONAL, scanner.addDocDependencies(debianDeps.get(OPTIONAL), versionedPackagesAndDependencies)); } debianDeps.putInProperties(depVars); if (packageVersion != null) { depVars.put("maven.UpstreamPackageVersion", packageVersion); } // Write everything to debian/substvars Substvars.write(outputDirectory, packageName, depVars); } public void setBaseDir(File baseDir) { this.baseDir = baseDir; if (pomTransformer.getListOfPOMs() != null) { pomTransformer.getListOfPOMs().setBaseDir(baseDir); } } public void setListOfPoms(File listOfPoms) { if (pomTransformer.getListOfPOMs() == null) { pomTransformer.setListOfPOMs(new ListOfPOMs(listOfPoms)); } else { pomTransformer.getListOfPOMs().setListOfPOMsFile(listOfPoms); } pomTransformer.getListOfPOMs().setBaseDir(baseDir); } private Repository getRepository() { if (repository == null && mavenRepo != null) { repository = new Repository(mavenRepo); repository.scan(); } return repository; } public void solveDependencies() { pomTransformer.setRepository(getRepository()); pomTransformer.usePluginVersionsFromRepository(); IOUtil.mkDirIfNotExists(outputDirectory); if (exploreProjects) { File pom; if (pomTransformer.getListOfPOMs().getPomOptions().isEmpty()) { pom = new File(baseDir, "pom.xml"); if (pom.exists()) { pomTransformer.getListOfPOMs().addPOM("pom.xml"); } else { pom = new File(baseDir, "debian/pom.xml"); if (pom.exists()) { pomTransformer.getListOfPOMs().addPOM("debian/pom.xml"); } else { System.err.println("Cannot find the POM file"); return; } } } else { pom = new File(baseDir, pomTransformer.getListOfPOMs().getFirstPOM()); } resolveDependencies(pom); } else { pomTransformer.getListOfPOMs().foreachPoms(new POMHandler() { public void handlePOM(File pomFile, boolean noParent, boolean hasPackageVersion) throws Exception { resolveDependencies(pomFile); } public void ignorePOM(File pomFile) throws Exception { } }); } for (ToResolve tr : toResolve) { tr.resolve(); } if (!issues.isEmpty()) { System.err.println("ERROR:"); for (String issue : issues) { System.err.println(issue); } System.err.println("--------"); } } private void resolveDependencies(File projectPom) { if (pomTransformer.getListOfPOMs().getOrCreatePOMOptions(projectPom) != null && pomTransformer.getListOfPOMs().getOrCreatePOMOptions(projectPom).isIgnore()) { return; } System.out.println("Analysing " + IOUtil.relativePath(baseDir, projectPom) + "..."); try { POMInfo pom = getPOM(projectPom); pom.setProperties(new HashMap<String, String>()); pom.getProperties().put("debian.package", packageName); if (pomTransformer.getListOfPOMs().getOrCreatePOMOptions(projectPom).isNoParent()) { pom.setParent(null); } else if (pom.getParent() != null && !pom.getParent().isSuperPom()) { boolean oldNoParent = pomTransformer.getListOfPOMs().getOrCreatePOMOptions(projectPom).isNoParent(); // Don't mark the parent dependency as 'build time' dependency because once installed, // the POM for this project will always need the parent POM to be available Dependency parent = resolveDependency(pom.getParent(), projectPom, false, false, false, true); // The user may have set or unset the --no-parent option, if so we update the POM to include or not the // parent according to the user's choice if (pomTransformer.getListOfPOMs().getOrCreatePOMOptions(projectPom).isNoParent() != oldNoParent) { pomInfoCache.remove(projectPom.getAbsolutePath()); pom = getPOM(projectPom); } pom.setParent(parent); // If the parent is found, search the parent POM and update current POM if (parent != null) { POMInfo parentPOM = getRepository().searchMatchingPOM(parent); pom.setParentPOM(parentPOM); } } getRepository().registerPom(projectPom, pom); // Also register automatically the test jar which may accompany the current jar and be // used in another module of the same project if (pom.getThisPom().isJar()) { POMInfo testPom = (POMInfo) pom.clone(); testPom.getThisPom().setType("test-jar"); getRepository().registerPom(projectPom, testPom); } knownProjectDependencies.add(pom.getThisPom()); if (interactive && packageVersion == null) { String question = "Enter the upstream version for the package."; String v = new SimpleQuestion(question, pom.getOriginalVersion()).ask(); if (v.isEmpty()) { v = pom.getOriginalVersion(); } packageVersion = v; } if (pom.getOriginalVersion().equals(packageVersion)) { pom.getProperties().put("debian.hasPackageVersion", "true"); pomTransformer.getListOfPOMs().getOrCreatePOMOptions(projectPom).setHasPackageVersion(true); } if (filterModules) { boolean includeModule = new YesNoQuestion("Include the module " + IOUtil.relativePath(baseDir, projectPom) + " ?", true).ask(); if (!includeModule) { pomTransformer.getListOfPOMs().getOrCreatePOMOptions(projectPom).setIgnore(true); pomTransformer.getRulesFiles().get(IGNORE).add(DependencyRule.newToMatch(pom.getThisPom())); return; } } projectPoms.add(pom.getThisPom()); // Previous rule from another run boolean explicitlyMentionedInRules = false; for (DependencyRule previousRule : pomTransformer.getRulesFiles().get(RULES).findMatchingRules(pom.getThisPom())) { if (!previousRule.explicitlyMentions(pom.getThisPom())) { explicitlyMentionedInRules = true; break; } } if (interactive && !explicitlyMentionedInRules && !pom.getThisPom().isPlugin()) { Rule selectedRule = askForVersionRule(pom.getThisPom()); versionToRules.put(pom.getThisPom().getVersion(), selectedRule); if (selectedRule.getPattern().equals("CUSTOM")) { String rule = new SimpleQuestion("Enter the pattern for your custom rule (in the form s/regex/replace/)").ask().toLowerCase(); selectedRule = new Rule(rule, "My custom rule " + rule); defaultRules.add(selectedRule); } pomTransformer.getRulesFiles().get(RULES).add(new DependencyRule(pom.getThisPom().getGroupId(), pom.getThisPom().getArtifactId(), pom.getThisPom().getType(), selectedRule.toString())); POMInfo transformedPom = pom.newPOMFromRules(pomTransformer.getRulesFiles().get(RULES).getRules(), getRepository()); getRepository().registerPom(projectPom, transformedPom); projectPoms.add(transformedPom.getThisPom()); if (pom.getThisPom().isBundle()) { String question2 = pom.getThisPom().getGroupId() + ":" + pom.getThisPom().getArtifactId() + " is a bundle.\n" + "Inform mh_make that dependencies of type jar which may match this library should be transformed into bundles automatically?"; boolean transformJarsIntoBundle = new YesNoQuestion(question2, true).ask(); if (transformJarsIntoBundle) { String transformBundleRule = pom.getThisPom().getGroupId() + " " + pom.getThisPom().getArtifactId() + " s/jar/bundle/ " + selectedRule.toString(); pomTransformer.getRulesFiles().get(PUBLISHED).add(new DependencyRule(transformBundleRule)); } } } if (pom.getParent() != null && !pom.getParent().isSuperPom()) { POMInfo parentPom = getRepository().searchMatchingPOM(pom.getParent()); if (parentPom == null || parentPom.equals(getRepository().getSuperPOM())) { pomTransformer.getListOfPOMs().getOrCreatePOMOptions(projectPom).setNoParent(true); } if (!baseDir.equals(projectPom.getParentFile())) { System.out.println("Checking the parent dependency in the sub project " + IOUtil.relativePath(baseDir, projectPom)); resolveDependency(pom.getParent(), projectPom, false, false, false, true); } } toResolve.add(new ToResolve(projectPom, DependencyType.DEPENDENCIES, false, false, false)); toResolve.add(new ToResolve(projectPom, DependencyType.DEPENDENCY_MANAGEMENT_LIST, false, false, true)); toResolve.add(new ToResolve(projectPom, DependencyType.PLUGINS, true, true, false)); toResolve.add(new ToResolve(projectPom, DependencyType.PLUGIN_DEPENDENCIES, true, true, false)); toResolve.add(new ToResolve(projectPom, DependencyType.PLUGIN_MANAGEMENT, true, true, true)); toResolve.add(new ToResolve(projectPom, DependencyType.REPORTING_PLUGINS, true, true, false)); toResolve.add(new ToResolve(projectPom, DependencyType.EXTENSIONS, true, true, false)); if (exploreProjects && !pom.getModules().isEmpty()) { if (interactive && !askedToFilterModules) { filterModules = !new YesNoQuestion("This project contains modules. Include all modules?", true).ask(); askedToFilterModules = true; } for (String module : pom.getModules()) { resolveDependencies(new File(projectPom.getParent(), module + "/pom.xml")); } } } catch (Exception ex) { log.log(Level.SEVERE, "Error while resolving " + projectPom + ": " + ex.getMessage()); log.log(Level.SEVERE, "", ex); System.exit(1); } } /** * Asks the user to specify the substitution rule for the version. * * @param dependency * @return the version rule selected */ public Rule askForVersionRule(Dependency dependency) { String question = "\n" + "Version of " + dependency.getGroupId() + ":" + dependency.getArtifactId() + " is " + dependency.getVersion() + "\nChoose how the version will be transformed:"; List<Rule> choices = getVersionRules(dependency.getVersion()); // select the default choice (either the previously selected rule or the 'debian' version rule) int defaultChoice = 1; // the 'debian' version rule is the first one of the default rules if (versionToRules.containsKey(dependency.getVersion())) { Rule previouslySelectedRule = versionToRules.get(dependency.getVersion()); if (choices.contains(previouslySelectedRule)) { defaultChoice = choices.indexOf(previouslySelectedRule); } } List<String> choicesDescriptions = new ArrayList<String>(); for (Rule choice : choices) { choicesDescriptions.add(choice.getDescription()); } int choice = new ChoiceQuestion(question, defaultChoice, choicesDescriptions).ask(); return choices.get(choice); } /** * Returns the substitution rules for the specified version. */ private List<Rule> getVersionRules(String version) { List<Rule> rules = new ArrayList<Rule>(); // add the 1.0 -> 1.x rule Pattern p = Pattern.compile("(\\d+)(\\..*)"); Matcher matcher = p.matcher(version); if (matcher.matches()) { String mainVersion = matcher.group(1); Rule mainVersionRule = new Rule("s/" + mainVersion + "\\..*/" + mainVersion + ".x/", "Replace all versions starting by " + mainVersion + ". with " + mainVersion + ".x"); rules.add(mainVersionRule); } // add the default rules rules.addAll(defaultRules); return rules; } private POMInfo getPOM(File projectPom) throws XMLStreamException, IOException { POMInfo info = pomInfoCache.get(projectPom.getAbsolutePath()); if (info != null) { return info; } File tmpDest = File.createTempFile("pom", ".tmp", baseDir); tmpDest.deleteOnExit(); POMOptions options = pomTransformer.getListOfPOMs().getOrCreatePOMOptions(projectPom); boolean noParent = false; boolean hasPackageVersion = false; if (options != null) { noParent = options.isNoParent(); hasPackageVersion = options.getHasPackageVersion(); } info = pomTransformer.transformPom(projectPom, tmpDest, noParent, hasPackageVersion, false, false, null, null, true); pomInfoCache.put(projectPom.getAbsolutePath(), info); return info; } private POMInfo getOriginalPOM(File projectPom) throws XMLStreamException, IOException { POMInfo info = originalPomInfoCache.get(projectPom.getAbsolutePath()); if (info != null) { return info; } info = pomTransformer.readPom(projectPom); originalPomInfoCache.put(projectPom.getAbsolutePath(), info); return info; } private static boolean canBeSkippedBecauseAntIsUsedForPackaging(Dependency thisPom, String packageType, Dependency dependency, boolean runTests, boolean verbose) { if (!packageType.equals("ant")) return false; if (dependency.isPlugin() && !thisPom.isPom()) { if(verbose) System.out.println("[skipped - Maven plugins are not used during a build with Ant]"); return true; } if (!runTests && "test".equals(dependency.getScope())) { if(verbose) System.out.println("[skipped - Tests are not executed during the build]"); return true; } return false; } private Dependency resolveDependency(Dependency dependency, File sourcePom, boolean buildTime, boolean mavenExtension, boolean management, boolean resolvingParent) throws DependencyNotFoundException { if (containsDependencyIgnoreVersion(knownProjectDependencies, dependency)) { return dependency; } if (containsDependencyIgnoreVersion(ignoredDependencies, dependency) || (management && isDefaultMavenPlugin(dependency))) { return null; } if (resolvingParent && dependency.isSuperPom()) { return dependency; } String sourcePomLoc = sourcePom.getAbsolutePath(); String baseDirPath = baseDir.getAbsolutePath(); sourcePomLoc = sourcePomLoc.substring(baseDirPath.length() + 1, sourcePomLoc.length()); if (verbose) { System.out.println("Resolving " + dependency + (dependency.getScope() == null ? "" : " of scope " + dependency.getScope()) + "..."); } // First let the packager mark as ignored those dependencies which should be ignored if (ignoreDependencyQuestion.askIgnoreUnnecessaryDependency(dependency, sourcePomLoc, runTests, generateJavadoc)) { ignoredDependencies.add(dependency); pomTransformer.getRulesFiles().get(IGNORE).add(new DependencyRule(dependency.getGroupId(), dependency.getArtifactId(), "*", "*")); if (verbose) { System.out.println("[ignored]"); } return null; } // Automatically skip some dependencies when ant packaging is used try { if (canBeSkippedBecauseAntIsUsedForPackaging(getPOM(sourcePom).getThisPom(), packageType, dependency, runTests, verbose)) { // Even if we skip the dependency, try to locate its associated maven rules, // as this may be useful later - but never fail if the dependency is not found. POMInfo pom = getRepository().searchMatchingPOM(dependency); if (pom != null) { pomTransformer.getRulesFiles().get(RULES).addAll(pom.getPublishedRules()); } return null; } } catch (XMLStreamException e) { e.printStackTrace(); // TODO Auto-generated catch block } catch (IOException e) { e.printStackTrace(); } POMInfo pom = getRepository().searchMatchingPOM(dependency); try { if (pom == null && dependency.getVersion() == null) { POMInfo containerPom = getPOM(sourcePom); String version = containerPom.getVersionFromManagementDependency(dependency); dependency.setVersion(version); if (version != null) { pom = getRepository().searchMatchingPOM(dependency); } else { System.out.println("In " + sourcePomLoc + ", cannot find the version for dependency " + dependency + " from this POM or its parent POMs"); if (pomTransformer.getListOfPOMs().getOrCreatePOMOptions(sourcePom).isNoParent()) { Dependency originalParent = getOriginalPOM(sourcePom).getParent(); System.out.println("[warning] Option --no-parent has been set for POM file " + sourcePomLoc + ", maybe it was not a good idea and you should first package the parent POM " + originalParent); } } } } catch (Exception e) { e.printStackTrace(); } if (pom == null && dependency.getVersion() != null) { List<POMInfo> poms = getRepository().searchMatchingPOMsIgnoreVersion(dependency); for (POMInfo potentialPom : poms) { for(DependencyRule rule : potentialPom.getPublishedRules()) { if (rule.matches(dependency) && rule.apply(dependency).equals(potentialPom.getThisPom())) { pom = potentialPom; pomTransformer.getRulesFiles().get(RULES).add(rule); } } } } if (pom == null && dependency.getVersion() == null) { // Set a dummy version and try again for (int version = 0; version < 10; version++) { dependency.setVersion(version + ".0"); pom = getRepository().searchMatchingPOM(dependency); if (pom != null) { System.out.println("Use best guess version: " + dependency.getVersion() + " for " + dependency.getGroupId() + ":" + dependency.getArtifactId()); break; } dependency.setVersion(null); } } if (pom == null && dependency.isPlugin()) { List<POMInfo> matchingPoms = getRepository().searchMatchingPOMsIgnoreVersion(dependency); if (matchingPoms.size() > 1) { issues.add(sourcePomLoc + ": More than one version matches the plugin " + dependency.getGroupId() + ":" + dependency.getArtifactId() + ":" + dependency.getVersion()); } if (!matchingPoms.isEmpty()) { pom = matchingPoms.get(0); // Don't add a rule to force the version of a Maven plugin, it's now done // automatically at build time } } // Ignore fast cases if (pom == null) { if (management) { if (verbose) System.out.println("[skipped dependency or plugin management]"); return null; } else if (dependency.isPlugin() && packageType.equals("ant")) { if (verbose) System.out.println("[skipped - not used in Ant build]"); return null; } } // In case we didn't find anything for "jar" packaging type, just check for a "bundle" type inside repository. // Some jars have been upgraded to OSGi bundles as OSGi metadata has been added to them. // // drazzib: I'm not sure this is really the right way to fix that (ie. maybe we should install "bundle" artifacts // directly with "jar" type inside Debian ?). // // ludovicc: a complex issue, I believe that libraries which evolve from a jar type to a bundle type should // inform packagers with a rule of the form // '<groupId> <artifactId> s/jar/bundle/ <versionRule>' // in other words, the packager of the library should add a published rule which will transform matching // libraries from jar type into bundle types, and apply as well the version substitution (for example to 2.x) // for Debian. // if (pom == null && dependency.isJar()) { if (verbose) System.out.println("[check dependency with bundle type]"); Dependency bundleDependency = dependency.builder().setType("bundle").build(); pom = getRepository().searchMatchingPOM(bundleDependency); if (pom != null) { dependency = bundleDependency; for (DependencyRule rule: pom.getPublishedRules()) { if (rule.matchesAndPreservesGroupArtifactAndType(dependency)) { pomTransformer.getRulesFiles().get(RULES).add(new DependencyRule( pom.getThisPom().getGroupId(), pom.getThisPom().getArtifactId(), "s/jar/bundle/", rule.getVersionRule().toString())); } } } } if (pom == null) { if (resolvingParent && ignoreDependencyQuestion.askIgnoreDependency(sourcePomLoc, dependency, "The parent POM cannot be found in the Maven repository for Debian. Ignore it?")) { pomTransformer.getListOfPOMs().getOrCreatePOMOptions(sourcePom).setNoParent(true); if (verbose) System.out.println("[no-parent]"); return null; } boolean ignoreDependency = ignoreDependencyQuestion.askIgnoreDocOrReportPlugin(sourcePomLoc, dependency); if(!ignoreDependency) { String issue = ignoreDependencyQuestion.askIgnoreNeededDependency(sourcePomLoc, dependency); if(issue.isEmpty()) { ignoreDependency = true; } else { issues.add(issue); } } if (ignoreDependency) { ignoredDependencies.add(dependency); pomTransformer.getRulesFiles().get(IGNORE).add(new DependencyRule(dependency.getGroupId(), dependency.getArtifactId(), "*", "*")); if (verbose) System.out.println("[ignored]"); return null; } // We're not ignoring the dependency DebianDependency pkg = scanner.searchPkgContainingPom(dependency); if (pkg != null) { String installedVersion = scanner.getPackageVersion(pkg, true); if (installedVersion != null) { System.out.println("[error] Package " + pkg + " (" + installedVersion + ") is already installed and contains a possible match," ); System.out.println("but I cannot resolve library " + dependency + " in it."); System.out.println("[error] Please check manually that the library is up to date, otherwise it may be necessary to package version " + dependency.getVersion() + " in Debian."); } else { System.out.println("[warning] Please install the missing dependency. Run the following command in another terminal:"); System.out.println(" sudo apt-get install " + pkg); } } if (interactive && pkg == null) { pkg = scanner.searchPkgContainingJar(dependency); if (pkg != null) { String question = "[error] Package " + pkg + " does not contain Maven dependency " + dependency + " but there seem to be a match\n" + "If the package contains already Maven artifacts but the names don't match, try to enter a substitution rule\n" + "of the form s/groupId/newGroupId/ s/artifactId/newArtifactId/ jar s/version/newVersion/ here:"; String newRule = new SimpleQuestion(question).ask(); if (!newRule.isEmpty()) { DependencyRule userRule = new DependencyRule(newRule); pomTransformer.getRulesFiles().get(RULES).add(userRule); System.out.println("Please suggest the maintainer of package " + pkg + " to add this rule to debian/maven.publishedRules"); return resolveDependency(dependency.applyRules(Arrays.asList(userRule)), sourcePom, buildTime, mavenExtension, management, false); } } else { String newRule = new SimpleQuestion( "[error] Cannot resolve Maven dependency " + dependency + ". If you know a package that contains a compatible dependency,\n" + "try to enter a substitution rule of the form s/groupId/newGroupId/ s/artifactId/newArtifactId/ jar s/version/newVersion/ here:\n").ask(); while (!newRule.isEmpty()) { DependencyRule userRule = new DependencyRule(newRule); Dependency newDependency = dependency.applyRules(Arrays.asList(userRule)); if (newDependency.equals(dependency)) { newRule = new SimpleQuestion("Your rule doesn't seem to apply on " + dependency + "Please enter a substitution rule of the form s/groupId/newGroupId/ s/artifactId/newArtifactId/ jar s/version/newVersion/ here," + "or press <Enter> to give up").ask(); } else { pomTransformer.getRulesFiles().get(RULES).add(userRule); System.out.println("Rescanning /usr/share/maven-repo..."); pomTransformer.getRepository().scan(); return resolveDependency(dependency.applyRules(Arrays.asList(userRule)), sourcePom, buildTime, mavenExtension, management, false); } } } } if (interactive && new YesNoQuestion("Try again to resolve the dependency?", true).ask()) { System.out.println("Rescanning /usr/share/maven-repo..."); pomTransformer.getRepository().scan(); // Clear caches scanner = scanner.newInstanceWithFreshCaches(); return resolveDependency(dependency, sourcePom, buildTime, mavenExtension, management, false); } if (verbose) System.out.println("[error]"); throw new DependencyNotFoundException(dependency); } // Handle the case of Maven plugins built and used in a multi-module build: // they need to be added to maven.cleanIgnoreRules to avoid errors during // a mvn clean if (dependency.isPlugin() && containsDependencyIgnoreVersion(projectPoms, dependency)) { String ruleDef = dependency.getGroupId() + " " + dependency.getArtifactId() + " maven-plugin *"; pomTransformer.getRulesFiles().get(CLEAN).add(new DependencyRule(ruleDef)); } // Discover the library to import for the dependency DebianDependency pkg = getPackage(pom, sourcePomLoc); if (pkg != null && !pkg.equals(packageName)) { DebianDependency libraryWithVersionConstraint; if (pom.getOriginalVersion() != null && (pom.getProperties().containsKey("debian.hasPackageVersion"))) { String version = dependency.getVersion(); if (version == null || (pom.getOriginalVersion() != null && version.compareTo(pom.getOriginalVersion()) > 0)) { version = pom.getOriginalVersion(); } libraryWithVersionConstraint = new DebianDependency(pkg.getPackageName(), version); } else { libraryWithVersionConstraint = pkg; } if (!management) { if (buildTime) { if ("test".equals(dependency.getScope())) { debianDeps.add(TEST, libraryWithVersionConstraint); } else if (dependency.isPlugin()) { if (!packageType.equals("ant")) { debianDeps.add(COMPILE, libraryWithVersionConstraint); } } else if (mavenExtension) { if (!packageType.equals("ant")) { debianDeps.add(COMPILE, libraryWithVersionConstraint); } } else { debianDeps.add(COMPILE, libraryWithVersionConstraint); } } else { if ("test".equals(dependency.getScope())) { debianDeps.add(TEST, libraryWithVersionConstraint); } else if (dependency.isOptional()) { debianDeps.add(OPTIONAL, libraryWithVersionConstraint); } else if (!"provided".equals(dependency.getScope())) { debianDeps.add(RUNTIME, libraryWithVersionConstraint); } } } versionedPackagesAndDependencies.put(libraryWithVersionConstraint, dependency); } pomTransformer.getRulesFiles().get(RULES).addAll(pom.getPublishedRules()); if (verbose) { System.out.println("Dependency " + dependency + " found in package " + pkg); System.out.println("[ok]"); System.out.println(); } if (resolvingParent) { try { POMInfo containerPom = getPOM(sourcePom); containerPom.setParentPOM(pom); } catch (Exception e) { e.printStackTrace(); } } return pom.getThisPom(); } private DebianDependency getPackage(POMInfo pom, String sourcePomLoc) { DebianDependency pkg = null; if (pom.getProperties() != null) { pkg = new DebianDependency(pom.getProperties().get("debian.package")); } if (pkg == null) { Dependency dependency = pom.getThisPom(); issues.add(sourcePomLoc + ": Dependency is missing the Debian properties in its POM: " + dependency.getGroupId() + ":" + dependency.getArtifactId() + ":" + dependency.getVersion()); File pomFile = new File(mavenRepo, dependency.getGroupId().replace(".", "/") + "/" + dependency.getArtifactId() + "/" + dependency.getVersion() + "/" + dependency.getArtifactId() + "-" + dependency.getVersion() + ".pom"); pkg = scanner.searchPkg(pomFile); } return pkg; } private boolean containsDependencyIgnoreVersion(Collection<Dependency> dependencies, Dependency dependency) { for (Dependency ignoredDependency : dependencies) { if (ignoredDependency.equalsIgnoreVersion(dependency)) { return true; } } return false; } public static void main(String[] args) { if (args.length == 0 || "-h".equals(args[0]) || "--help".equals(args[0])) { System.out.println("Purpose: Solve the dependencies in the POM(s)."); System.out.println("Usage: [option]"); System.out.println(""); System.out.println("Options:"); System.out.println(" -v, --verbose: be extra verbose"); System.out.println(" -p<package>, --package=<package>: name of the Debian package containing"); System.out.println(" this library"); // System.out.println(" -r<rules>, --rules=<rules>: path to the file containing the"); // System.out.println(" extra rules to apply when cleaning the POM"); // System.out.println(" -i<rules>, --published-rules=<rules>: path to the file containing the"); // System.out.println(" extra rules to publish in the property debian.mavenRules in the cleaned POM"); System.out.println(" --ant: use ant for the packaging"); System.out.println(" --run-tests: run the unit tests"); System.out.println(" --generate-javadoc: generate Javadoc"); System.out.println(" --non-interactive: non interactive session"); System.out.println(" --offline: offline mode for Debian build compatibility"); System.out.println(" -m<repo root>--maven-repo=<repo root>: location of the Maven repository,"); System.out.println(" used to force the versions of the Maven plugins used in the current"); System.out.println(" POM file with the versions found in the repository"); System.out.println(" --base-directory: path to root directory of package"); System.out.println(" --non-explore: doesn't explore directories for pom.xml"); System.out.println(" --build: Don't write files to debian folder other than substvars"); return; } // Default values boolean verbose = false; String debianPackage = ""; String packageType = "maven"; File mavenRepo = null; File baseDirectory = new File("."); boolean exploreProjects = true; boolean runTests = false; boolean generateJavadoc = false; boolean interactive = true; boolean offline = false; boolean build = false; // Parse parameters int i = inc(-1, args); while (i < args.length && (args[i].trim().startsWith("-") || args[i].trim().isEmpty())) { String arg = args[i].trim(); if ("--verbose".equals(arg) || "-v".equals(arg)) { verbose = true; } else if ("--debug".equals(arg)) { log.setLevel(Level.FINEST); } else if (arg.startsWith("-p")) { debianPackage = arg.substring(2); } else if (arg.startsWith("--package=")) { debianPackage = arg.substring("--package=".length()); } else if (arg.equals("--ant")) { packageType = "ant"; } else if (arg.equals("--run-tests")) { runTests = true; } else if (arg.equals("--generate-javadoc")) { generateJavadoc = true; } else if (arg.equals("--non-interactive")) { interactive = false; } else if (arg.equals("--offline")) { offline = true; } else if (arg.startsWith("-m")) { mavenRepo = new File(arg.substring(2)); } else if (arg.startsWith("--maven-repo=")) { mavenRepo = new File(arg.substring("--maven-repo=".length())); } else if (arg.startsWith("-b")) { baseDirectory = new File(arg.substring(2)); } else if (arg.startsWith("--base-directory=")) { baseDirectory = new File(arg.substring("--base-directory=".length())); } else if (arg.equals("--non-explore")) { exploreProjects = false; } else if (arg.equals("--build")) { build = true; } i = inc(i, args); } File outputDirectory = new File(baseDirectory, "debian"); DependenciesSolver solver = new DependenciesSolver(outputDirectory, new PackageScanner(offline), interactive); solver.generateJavadoc = generateJavadoc; solver.runTests = runTests; solver.exploreProjects = exploreProjects; solver.setBaseDir(baseDirectory); solver.packageName = debianPackage; solver.packageType = packageType; File poms = new File(solver.outputDirectory, debianPackage + ".poms"); solver.setListOfPoms(poms); if (mavenRepo != null) { Repository repository = new Repository(mavenRepo); solver.pomTransformer.setRepository(repository); solver.pomTransformer.usePluginVersionsFromRepository(); } if (verbose) { String msg = "Solving dependencies for package " + debianPackage; if (solver.runTests) { msg += " (tests are included)"; } if (solver.generateJavadoc) { msg += " (documentation is included)"; } System.out.println(msg); solver.verbose = true; } solver.solveDependencies(); // Don't overwrite the poms or rules files during a build if (!build) { solver.pomTransformer.getListOfPOMs().save(); solver.pomTransformer.getRulesFiles().save(outputDirectory); } // Do generate the substvars though solver.saveSubstvars(); if (!solver.issues.isEmpty()) { System.err.println("Some problems were found in this project, exiting..."); System.exit(1); } } private static int inc(int i, String[] args) { do { i++; } while (i < args.length && args[i].isEmpty()); return i; } }