Skip to content

Commit b93f1d7

Browse files
committed
Add Maven dependency generator
This is a Java implementation of Maven provides/requires generators from Javapackages project. One notable difference from the old Python generators is that the new Java generators don't have support for generating OSGi dependencies as they are no longer needed. There are a few other differences in cases the old Python generators didn't produce correct results. Most of test data was copied from Javapackages, but the actual generator code is a clean-room implementation. This code is the first working version that passes all tests, before any refactoring. Refactoring will be done later, when time allows.
1 parent e818607 commit b93f1d7

6 files changed

Lines changed: 2874 additions & 3 deletions

File tree

pom.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,22 @@
8686
<artifactId>commons-compress</artifactId>
8787
<version>1.22</version>
8888
</dependency>
89+
<dependency>
90+
<groupId>org.fedoraproject.xmvn</groupId>
91+
<artifactId>xmvn-api</artifactId>
92+
<version>4.2.0</version>
93+
</dependency>
94+
<dependency>
95+
<groupId>org.apache.maven</groupId>
96+
<artifactId>maven-model</artifactId>
97+
<version>3.9.9</version>
98+
</dependency>
99+
<dependency>
100+
<groupId>org.fedoraproject.xmvn</groupId>
101+
<artifactId>xmvn-core</artifactId>
102+
<version>4.2.0</version>
103+
<scope>runtime</scope>
104+
</dependency>
89105
<dependency>
90106
<groupId>org.junit.jupiter</groupId>
91107
<artifactId>junit-jupiter</artifactId>
Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
package org.fedoraproject.xmvn.generator.maven;
2+
3+
import java.io.BufferedInputStream;
4+
import java.io.DataInputStream;
5+
import java.io.EOFException;
6+
import java.io.IOException;
7+
import java.io.InputStream;
8+
import java.io.PrintWriter;
9+
import java.io.Reader;
10+
import java.io.StringWriter;
11+
import java.io.UncheckedIOException;
12+
import java.nio.file.Files;
13+
import java.nio.file.Path;
14+
import java.util.ArrayList;
15+
import java.util.LinkedHashMap;
16+
import java.util.LinkedHashSet;
17+
import java.util.List;
18+
import java.util.Map;
19+
import java.util.Set;
20+
import java.util.stream.Stream;
21+
import java.util.zip.GZIPInputStream;
22+
23+
import javax.xml.stream.XMLStreamException;
24+
25+
import org.apache.maven.model.Extension;
26+
import org.apache.maven.model.Model;
27+
import org.apache.maven.model.Plugin;
28+
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
29+
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
30+
31+
import org.fedoraproject.xmvn.artifact.Artifact;
32+
import org.fedoraproject.xmvn.artifact.DefaultArtifact;
33+
import org.fedoraproject.xmvn.generator.BuildContext;
34+
import org.fedoraproject.xmvn.generator.Collector;
35+
import org.fedoraproject.xmvn.generator.Generator;
36+
import org.fedoraproject.xmvn.generator.logging.Logger;
37+
import org.fedoraproject.xmvn.locator.ServiceLocatorFactory;
38+
import org.fedoraproject.xmvn.metadata.ArtifactAlias;
39+
import org.fedoraproject.xmvn.metadata.ArtifactMetadata;
40+
import org.fedoraproject.xmvn.metadata.Dependency;
41+
import org.fedoraproject.xmvn.metadata.PackageMetadata;
42+
import org.fedoraproject.xmvn.metadata.SkippedArtifactMetadata;
43+
import org.fedoraproject.xmvn.metadata.io.stax.MetadataStaxReader;
44+
import org.fedoraproject.xmvn.resolver.ResolutionRequest;
45+
import org.fedoraproject.xmvn.resolver.ResolutionResult;
46+
import org.fedoraproject.xmvn.resolver.Resolver;
47+
48+
class Subpackage {
49+
final Path path;
50+
boolean pomOnly = true;
51+
52+
Subpackage(Path path) {
53+
this.path = path;
54+
}
55+
}
56+
57+
class UniqueArtifact {
58+
final Subpackage pkg;
59+
final ArtifactMetadata amd;
60+
final String rpmVersion;
61+
final String namespace;
62+
final Set<Artifact> artifacts = new LinkedHashSet<>();
63+
boolean pom = true;
64+
65+
UniqueArtifact(Subpackage pkg, ArtifactMetadata amd) {
66+
this.pkg = pkg;
67+
this.amd = amd;
68+
this.rpmVersion = amd.getVersion().replace('-', '.');
69+
this.namespace = amd.getNamespace();
70+
}
71+
}
72+
73+
class MavenGenerator implements Generator {
74+
private final BuildContext context;
75+
private final Resolver resolver;
76+
77+
MavenGenerator(BuildContext context, Resolver resolver) {
78+
this.context = context;
79+
this.resolver = resolver;
80+
}
81+
82+
public MavenGenerator(BuildContext context) {
83+
this(context, new ServiceLocatorFactory().createServiceLocator().getService(Resolver.class));
84+
}
85+
86+
private boolean isCompressed(BufferedInputStream bis) throws IOException {
87+
try {
88+
bis.mark(2);
89+
DataInputStream ois = new DataInputStream(bis);
90+
int magic = Short.reverseBytes(ois.readShort()) & 0xFFFF;
91+
return magic == GZIPInputStream.GZIP_MAGIC;
92+
} catch (EOFException e) {
93+
return false;
94+
} finally {
95+
bis.reset();
96+
}
97+
}
98+
99+
private PackageMetadata readMetadata(Path path) throws IOException, XMLStreamException {
100+
try (InputStream fis = Files.newInputStream(path)) {
101+
try (BufferedInputStream bis = new BufferedInputStream(fis, 128)) {
102+
try (InputStream is = isCompressed(bis) ? new GZIPInputStream(bis) : bis) {
103+
MetadataStaxReader reader = new MetadataStaxReader();
104+
return reader.read(is);
105+
}
106+
}
107+
}
108+
}
109+
110+
private String formatDep(Artifact art, String pkgver, String ns) {
111+
boolean cusExt = !art.getExtension().equals(Artifact.DEFAULT_EXTENSION);
112+
boolean cusCla = !art.getClassifier().equals("");
113+
boolean cusVer = !art.getVersion().equals(Artifact.DEFAULT_VERSION);
114+
StringBuilder sb = new StringBuilder();
115+
if (ns != null && !ns.isBlank()) {
116+
sb.append(ns);
117+
sb.append("-");
118+
}
119+
sb.append("mvn(");
120+
sb.append(art.getGroupId());
121+
sb.append(":");
122+
sb.append(art.getArtifactId());
123+
if (cusCla || cusExt) {
124+
sb.append(":");
125+
}
126+
if (cusExt) {
127+
sb.append(art.getExtension());
128+
}
129+
if (cusCla) {
130+
sb.append(":");
131+
sb.append(art.getClassifier());
132+
}
133+
if (cusCla || cusExt || cusVer) {
134+
sb.append(":");
135+
}
136+
if (cusVer) {
137+
sb.append(art.getVersion());
138+
}
139+
sb.append(")");
140+
if (pkgver != null) {
141+
sb.append(" = ");
142+
sb.append(pkgver);
143+
}
144+
return sb.toString();
145+
}
146+
147+
private void error(String msg) {
148+
context.eval("%{error:" + msg + "}");
149+
}
150+
151+
private String coalesce(String... strings) {
152+
for (String s : strings) {
153+
if (s != null && !s.isEmpty()) {
154+
return s;
155+
}
156+
}
157+
return null;
158+
}
159+
160+
private String resolveDep(Artifact dep, Map<Artifact, List<UniqueArtifact>> myArtifacts, Subpackage pmd) {
161+
for (Artifact depa : List.of(dep, dep.setVersion(Artifact.DEFAULT_VERSION))) {
162+
List<UniqueArtifact> umds = myArtifacts.get(depa);
163+
if (umds != null) {
164+
UniqueArtifact umd = umds.getFirst();
165+
if (umd.pkg.path.equals(pmd.path)) {
166+
// Self require
167+
return null;
168+
}
169+
return formatDep(depa, umd.rpmVersion, umd.namespace);
170+
}
171+
}
172+
ResolutionRequest req = new ResolutionRequest(dep);
173+
ResolutionResult res = resolver.resolve(req);
174+
if (res.getArtifactPath() == null) {
175+
return null;
176+
}
177+
String ns = res.getNamespace();
178+
String cver = res.getCompatVersion();
179+
Artifact depa = dep.setVersion(cver != null ? cver : Artifact.DEFAULT_VERSION);
180+
return formatDep(depa, null, ns);
181+
}
182+
183+
@Override
184+
public void generate(Collector collector) {
185+
Path buildRoot = Path.of(context.eval("%{buildroot}"));
186+
Path prefix = buildRoot.resolve("usr/share/maven-metadata");
187+
Map<Artifact, List<UniqueArtifact>> myArtifacts = new LinkedHashMap<>();
188+
Set<Artifact> skipped = new LinkedHashSet<>();
189+
List<UniqueArtifact> umds = new ArrayList<>();
190+
if (Files.isDirectory(prefix)) {
191+
try (Stream<Path> filePaths = Files.find(prefix, 1, (path, attr) -> attr.isRegularFile())) {
192+
for (Path filePath : filePaths.toList()) {
193+
PackageMetadata pmd = readMetadata(filePath);
194+
Subpackage md = new Subpackage(filePath);
195+
for (ArtifactMetadata amd : pmd.getArtifacts()) {
196+
UniqueArtifact umd = new UniqueArtifact(md, amd);
197+
umds.add(umd);
198+
List<Artifact> arts = new ArrayList<>();
199+
arts.add(amd.toArtifact());
200+
for (ArtifactAlias alias : amd.getAliases()) {
201+
arts.add(new DefaultArtifact(alias.getGroupId(), alias.getArtifactId(),
202+
alias.getExtension(), alias.getClassifier(), Artifact.DEFAULT_VERSION));
203+
}
204+
umd.pom = true;
205+
for (Artifact art : arts) {
206+
if (!"pom".equals(art.getExtension())) {
207+
umd.pom = false;
208+
}
209+
if (amd.getCompatVersions().isEmpty()) {
210+
umd.artifacts.add(art.setVersion(Artifact.DEFAULT_VERSION));
211+
} else {
212+
for (String ver : amd.getCompatVersions()) {
213+
umd.artifacts.add(art.setVersion(ver));
214+
}
215+
}
216+
}
217+
md.pomOnly &= umd.pom;
218+
for (Artifact vart : umd.artifacts) {
219+
myArtifacts.computeIfAbsent(vart, x -> new ArrayList<>()).add(umd);
220+
}
221+
}
222+
for (SkippedArtifactMetadata smd : pmd.getSkippedArtifacts()) {
223+
Artifact sart = new DefaultArtifact(smd.getGroupId(), smd.getArtifactId(), smd.getExtension(),
224+
smd.getClassifier(), Artifact.DEFAULT_VERSION);
225+
skipped.add(sart);
226+
}
227+
}
228+
} catch (IOException e) {
229+
throw new UncheckedIOException(e);
230+
} catch (XMLStreamException e) {
231+
throw new RuntimeException(e);
232+
}
233+
}
234+
for (UniqueArtifact umd : umds) {
235+
for (Artifact art : umd.artifacts) {
236+
collector.addProvides(umd.pkg.path, formatDep(art, umd.rpmVersion, umd.namespace));
237+
}
238+
if (umd.pkg.pomOnly) {
239+
Path pomPath = buildRoot.resolve(Path.of("/").relativize(Path.of(umd.amd.getPath())));
240+
MavenXpp3Reader pomReader = new MavenXpp3Reader();
241+
try (Reader reader = Files.newBufferedReader(pomPath)) {
242+
Model pom = pomReader.read(reader);
243+
if (!"pom".equals(pom.getPackaging())) {
244+
continue;
245+
}
246+
if (pom.getParent() != null) {
247+
String pgid = coalesce(pom.getParent().getGroupId(), pom.getGroupId());
248+
String paid = pom.getParent().getArtifactId();
249+
String pver = coalesce(pom.getParent().getVersion(), pom.getVersion());
250+
if (pgid != null && paid != null && pver != null) {
251+
String req = resolveDep(new DefaultArtifact(pgid, paid, "pom", pver), myArtifacts, umd.pkg);
252+
if (req != null) {
253+
collector.addRequires(umd.pkg.path, req);
254+
}
255+
}
256+
}
257+
if (pom.getBuild() != null) {
258+
for (Plugin plugin : pom.getBuild().getPlugins()) {
259+
// XXX naive approach, plugin management is not supported
260+
String pgid = coalesce(plugin.getGroupId(), "org.apache.maven.plugins");
261+
String paid = plugin.getArtifactId();
262+
String pver = coalesce(plugin.getVersion(), Artifact.DEFAULT_VERSION);
263+
if (paid != null) {
264+
String req = resolveDep(new DefaultArtifact(pgid, paid, pver), myArtifacts, umd.pkg);
265+
if (req != null) {
266+
collector.addRequires(umd.pkg.path, req);
267+
}
268+
}
269+
}
270+
for (Extension ext : pom.getBuild().getExtensions()) {
271+
String egid = ext.getGroupId();
272+
String eaid = ext.getArtifactId();
273+
String ever = coalesce(ext.getVersion(), Artifact.DEFAULT_VERSION);
274+
if (egid != null && eaid != null) {
275+
String req = resolveDep(new DefaultArtifact(egid, eaid, ever), myArtifacts, umd.pkg);
276+
if (req != null) {
277+
collector.addRequires(umd.pkg.path, req);
278+
}
279+
}
280+
}
281+
}
282+
} catch (IOException | XmlPullParserException e) {
283+
try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
284+
sw.append("Unable to generate POM dependencies: ");
285+
e.printStackTrace(pw);
286+
Logger.debug(sw.toString());
287+
} catch (IOException e1) {
288+
throw new UncheckedIOException(e1);
289+
}
290+
}
291+
}
292+
if (umd.pom && !umd.pkg.pomOnly) {
293+
continue;
294+
}
295+
for (Dependency dep : umd.amd.getDependencies()) {
296+
if (dep.isOptional() == null || dep.isOptional() == false) {
297+
Artifact depa = dep.toArtifact();
298+
if ("UNKNOWN".equals(dep.getResolvedVersion())) {
299+
// XXX improve error message
300+
error("Dependency on unresolved artifact: " + depa);
301+
continue;
302+
}
303+
Artifact rdepa = depa.setVersion(dep.getResolvedVersion());
304+
String ver = null;
305+
List<UniqueArtifact> depmds = myArtifacts.getOrDefault(rdepa, List.of());
306+
if (!depmds.isEmpty()) {
307+
ver = depmds.getFirst().rpmVersion;
308+
} else if (skipped.contains(rdepa)) {
309+
// XXX improve error message
310+
error("Dependency on skipped artifact: " + depa);
311+
continue;
312+
}
313+
if (depmds.stream().map(x -> x.pkg.path).noneMatch(umd.pkg.path::equals)) {
314+
collector.addRequires(umd.pkg.path, formatDep(rdepa, ver, dep.getNamespace()));
315+
}
316+
}
317+
}
318+
}
319+
}
320+
321+
@Override
322+
public String toString() {
323+
return "Maven generator";
324+
}
325+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.fedoraproject.xmvn.generator.maven;
2+
3+
import org.fedoraproject.xmvn.generator.BuildContext;
4+
import org.fedoraproject.xmvn.generator.Generator;
5+
import org.fedoraproject.xmvn.generator.GeneratorFactory;
6+
7+
public class MavenGeneratorFactory implements GeneratorFactory {
8+
@Override
9+
public Generator createGenerator(BuildContext context) {
10+
return new MavenGenerator(context);
11+
}
12+
}

src/main/rpm/macros.xmvngen

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# Class path of nested JVM. This should be a colon-separated list of
99
# JARs that contains xmvn-generator.jar, JARs with third-party
1010
# generators or hooks (if any), and all other dependencies.
11-
%__xmvngen_classpath %{_jnidir}/xmvn-generator.jar:%{_javadir}/objectweb-asm/asm.jar:%{_javadir}/commons-compress.jar:%{_javadir}/commons-io.jar
11+
%__xmvngen_classpath %{_jnidir}/xmvn-generator.jar:%{_javadir}/objectweb-asm/asm.jar:%{_javadir}/commons-compress.jar:%{_javadir}/commons-io.jar:%{_javadir}/xmvn/xmvn-api.jar:%{_javadir}/xmvn/xmvn-core.jar:%{_javadir}/maven/maven-model.jar:%{_javadir}/plexus/utils.jar
1212

1313
# Specify which dependency generators should be ran.
1414
# Values should be a space-separated list of qualified class names of
@@ -17,8 +17,9 @@
1717
# * org.fedoraproject.xmvn.generator.filesystem.FilesystemGeneratorFactory
1818
# * org.fedoraproject.xmvn.generator.jpscript.JPackageScriptGeneratorFactory
1919
# * org.fedoraproject.xmvn.generator.jpms.JPMSGeneratorFactory
20-
%__xmvngen_provides_generators org.fedoraproject.xmvn.generator.filesystem.FilesystemGeneratorFactory org.fedoraproject.xmvn.generator.jpscript.JPackageScriptGeneratorFactory org.fedoraproject.xmvn.generator.jpms.JPMSGeneratorFactory
21-
%__xmvngen_requires_generators org.fedoraproject.xmvn.generator.filesystem.FilesystemGeneratorFactory org.fedoraproject.xmvn.generator.jpscript.JPackageScriptGeneratorFactory
20+
# * org.fedoraproject.xmvn.generator.maven.MavenGeneratorFactory
21+
%__xmvngen_provides_generators org.fedoraproject.xmvn.generator.filesystem.FilesystemGeneratorFactory org.fedoraproject.xmvn.generator.jpscript.JPackageScriptGeneratorFactory org.fedoraproject.xmvn.generator.jpms.JPMSGeneratorFactory org.fedoraproject.xmvn.generator.maven.MavenGeneratorFactory
22+
%__xmvngen_requires_generators org.fedoraproject.xmvn.generator.filesystem.FilesystemGeneratorFactory org.fedoraproject.xmvn.generator.jpscript.JPackageScriptGeneratorFactory org.fedoraproject.xmvn.generator.maven.MavenGeneratorFactory
2223

2324
# Specify which post-install hooks should be ran.
2425
# Value should be a space-separated list of qualified class names of

0 commit comments

Comments
 (0)