Skip to content

Commit 78260b6

Browse files
committed
158: Support deploying transitive dependencies
1 parent dda23ea commit 78260b6

12 files changed

Lines changed: 605 additions & 151 deletions

File tree

java/pom.xml

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2-
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
1+
<project xmlns="http://maven.apache.org/POM/4.0.0"
2+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
34
<modelVersion>4.0.0</modelVersion>
45
<groupId>io.github.astonbitecode</groupId>
56
<artifactId>j4rs</artifactId>
@@ -55,8 +56,9 @@
5556
<maven.resources.plugin.version>3.3.1</maven.resources.plugin.version>
5657
<build.plugins.plugin.version>3.11.0</build.plugins.plugin.version>
5758
<jackson.version>2.15.2</jackson.version>
58-
<mockito.version>5.4.0</mockito.version>
59+
<mockito.version>5.21.0</mockito.version>
5960
<javafx.version>21.0.2</javafx.version>
61+
<mavenVersion>3.2.1</mavenVersion>
6062
</properties>
6163
<dependencies>
6264
<dependency>
@@ -69,6 +71,11 @@
6971
<artifactId>jackson-databind</artifactId>
7072
<version>${jackson.version}</version>
7173
</dependency>
74+
<dependency>
75+
<groupId>org.apache.maven</groupId>
76+
<artifactId>maven-aether-provider</artifactId>
77+
<version>${mavenVersion}</version>
78+
</dependency>
7279
<!-- Test dependencies -->
7380
<dependency>
7481
<groupId>junit</groupId>
@@ -118,8 +125,8 @@
118125
</executions>
119126
<configuration>
120127
<encoding>UTF-8</encoding>
121-
<source>1.8</source>
122-
<target>1.8</target>
128+
<source>11</source>
129+
<target>11</target>
123130
</configuration>
124131
</plugin>
125132
<plugin>
@@ -257,4 +264,4 @@
257264
</build>
258265
</profile>
259266
</profiles>
260-
</project>
267+
</project>

java/src/main/java/org/astonbitecode/j4rs/api/deploy/DeployUtils.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
import java.net.MalformedURLException;
1919

2020
public class DeployUtils {
21+
private DeployUtils() {
22+
23+
}
24+
2125
/**
2226
* Adds a jar to the classpath
2327
* @param fullJarPath The full path of the jar to add
@@ -30,4 +34,19 @@ static void addToClasspath(String fullJarPath) throws MalformedURLException {
3034
classLoader.add(jar.toURI().toURL());
3135
}
3236
}
37+
38+
static String generateArtifactName(String artifactId, String version, String qualifier, String artifactType) {
39+
StringBuilder jarName = new StringBuilder(String.format("%s-%s", artifactId, version));
40+
if (qualifier != null && !qualifier.isEmpty()) {
41+
jarName.append("-").append(qualifier);
42+
}
43+
jarName.append(".").append(artifactType);
44+
return jarName.toString();
45+
}
46+
47+
static boolean artifactExists(String groupId, String artifactId, String version, String qualifier, String artifactType, String deployTarget) {
48+
String artifactName = generateArtifactName(artifactId, version, qualifier, artifactType);
49+
String pathString = deployTarget + File.separator + artifactName;
50+
return new File(pathString).exists();
51+
}
3352
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
* Copyright 2026 astonbitecode Licensed under the Apache License, Version 2.0 (the "License"); you
3+
* may not use this file except in compliance with the License. You may obtain a copy of the License
4+
* at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software distributed under the License
9+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
10+
* or implied. See the License for the specific language governing permissions and limitations under
11+
* the License.
12+
*/
13+
package org.astonbitecode.j4rs.api.deploy;
14+
15+
import java.io.File;
16+
import java.io.IOException;
17+
import java.util.ArrayList;
18+
import java.util.HashMap;
19+
import java.util.List;
20+
import java.util.Map;
21+
import org.apache.maven.model.Dependency;
22+
import org.apache.maven.model.Model;
23+
import org.apache.maven.model.Repository;
24+
import org.apache.maven.model.building.DefaultModelBuilderFactory;
25+
import org.apache.maven.model.building.DefaultModelBuildingRequest;
26+
import org.apache.maven.model.building.FileModelSource;
27+
import org.apache.maven.model.building.ModelBuilder;
28+
import org.apache.maven.model.building.ModelBuildingResult;
29+
import org.apache.maven.model.building.ModelSource;
30+
import org.apache.maven.model.resolution.InvalidRepositoryException;
31+
import org.apache.maven.model.resolution.ModelResolver;
32+
import org.apache.maven.model.resolution.UnresolvableModelException;
33+
34+
public class MavenDeployer implements MavenDeployerApi {
35+
private static final String POM_TYPE = "pom";
36+
private final SimpleMavenDeployer simpleMavenDeployer;
37+
private final Map<String, SimpleMavenDeployer> additionalDeployers = new HashMap<>();
38+
39+
public MavenDeployer() {
40+
this.simpleMavenDeployer = new SimpleMavenDeployer();
41+
}
42+
43+
public MavenDeployer(String deployTarget) {
44+
this.simpleMavenDeployer = new SimpleMavenDeployer(deployTarget);
45+
}
46+
47+
public MavenDeployer(String repoBase, String deployTarget) {
48+
this.simpleMavenDeployer = new SimpleMavenDeployer(repoBase, deployTarget);
49+
}
50+
51+
public MavenDeployer(String repoBase, boolean checkLocalCache, String deployTarget) {
52+
this.simpleMavenDeployer = new SimpleMavenDeployer(repoBase, checkLocalCache, deployTarget);
53+
}
54+
55+
@Override
56+
public void deploy(String groupId, String artifactId, String version, String qualifier) throws IOException {
57+
this.deploy(groupId, artifactId, version, qualifier, "jar");
58+
}
59+
60+
@Override
61+
public void deploy(String groupId, String artifactId, String version, String qualifier, String artifactType) throws IOException {
62+
if (!DeployUtils.artifactExists(groupId, artifactId, version, qualifier, artifactType, artifactType)) {
63+
doDeploy(groupId, artifactId, version, qualifier, artifactType, gatherAllDeployers());
64+
if (!artifactType.equals(POM_TYPE)) {
65+
// For pom types the qualifiers should not be defined
66+
doDeploy(groupId, artifactId, version, "", POM_TYPE, gatherAllDeployers());
67+
}
68+
// For pom types the qualifiers should not be defined
69+
String pomArtifactName = DeployUtils.generateArtifactName(artifactId, version, "", POM_TYPE);
70+
String pathString = getDeployTarget() + File.separator + pomArtifactName;
71+
try {
72+
File pomFile = new File(pathString);
73+
List<Dependency> dependencies = parsePom(pomFile);
74+
if (dependencies != null) {
75+
for (Dependency dep : dependencies) {
76+
if (!dep.getArtifactId().contains("j4rs") && dep.getType().equals(artifactType) && !dep.getScope().equals("test") && !dep.getScope().equals("provided")) {
77+
// Deploy only for the needed os
78+
if (dep.getClassifier() == null || dep.getClassifier().length() == 0 || dep.getClassifier().contains(System.getProperty("os.name").toLowerCase())) {
79+
deploy(dep.getGroupId(), dep.getArtifactId(), dep.getVersion(), dep.getClassifier(), artifactType);
80+
}
81+
}
82+
if (!POM_TYPE.equals(artifactType)) {
83+
pomFile.delete();
84+
}
85+
}
86+
}
87+
} catch (Exception error) {
88+
error.printStackTrace();
89+
throw new IOException(error);
90+
}
91+
}
92+
}
93+
94+
List<Dependency> parsePom(File pomFile) throws Exception {
95+
final DefaultModelBuildingRequest modelBuildingRequest = new DefaultModelBuildingRequest().setPomFile(pomFile);
96+
modelBuildingRequest.setModelResolver(new J4rsMavenModelResolver());
97+
modelBuildingRequest.setSystemProperties(System.getProperties());
98+
ModelBuilder modelBuilder = new DefaultModelBuilderFactory().newInstance();
99+
ModelBuildingResult modelBuildingResult = modelBuilder.build(modelBuildingRequest);
100+
101+
Model model = modelBuildingResult.getEffectiveModel();
102+
List<Dependency> deps = model.getDependencies();
103+
return deps;
104+
}
105+
106+
public String getRepoBase() {
107+
return simpleMavenDeployer.getRepoBase();
108+
}
109+
110+
public String getDeployTarget() {
111+
return simpleMavenDeployer.getDeployTarget();
112+
}
113+
114+
private List<SimpleMavenDeployer> gatherAllDeployers() {
115+
List<SimpleMavenDeployer> deployers = new ArrayList<>();
116+
deployers.add(simpleMavenDeployer);
117+
deployers.addAll(additionalDeployers.values());
118+
return deployers;
119+
}
120+
121+
void doDeploy(String groupId, String artifactId, String version, String qualifier, String artifactType, List<SimpleMavenDeployer> deployers) throws IOException {
122+
if (deployers != null && deployers.size() > 0) {
123+
try {
124+
deployers.get(0).deploy(groupId, artifactId, version, qualifier, artifactType);
125+
} catch (IOException error) {
126+
if (deployers.size() == 1) {
127+
throw error;
128+
} else {
129+
List<SimpleMavenDeployer> reducedDeployers = deployers.subList(1, deployers.size() - 1);
130+
doDeploy(groupId, artifactId, version, qualifier, artifactType, reducedDeployers);
131+
}
132+
}
133+
}
134+
}
135+
136+
private class J4rsMavenModelResolver implements ModelResolver {
137+
138+
@Override
139+
public ModelSource resolveModel(String groupId, String artifactId, String version) throws UnresolvableModelException {
140+
141+
try {
142+
doDeploy(groupId, artifactId, version, "", POM_TYPE, gatherAllDeployers());
143+
String pomArtifactName = DeployUtils.generateArtifactName(artifactId, version, "", POM_TYPE);
144+
String pathString = getDeployTarget() + File.separator + pomArtifactName;
145+
return new FileModelSource(new File(pathString));
146+
} catch (IOException error) {
147+
throw new UnresolvableModelException("Could not resolve model", groupId, artifactId, version, error);
148+
}
149+
}
150+
151+
@Override
152+
public void addRepository(Repository repository) throws InvalidRepositoryException {
153+
additionalDeployers.put(repository.getUrl(), new SimpleMavenDeployer(getRepoBase(), repository.getUrl()));
154+
}
155+
156+
@Override
157+
public ModelResolver newCopy() {
158+
return new J4rsMavenModelResolver();
159+
}
160+
161+
}
162+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2026 astonbitecode
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
package org.astonbitecode.j4rs.api.deploy;
16+
17+
import java.io.IOException;
18+
19+
public interface MavenDeployerApi {
20+
21+
/**
22+
* Deploy a Maven artifact
23+
*
24+
* @param groupId The maven group ID
25+
* @param artifactId The maven artifact ID
26+
* @param version The artifact version
27+
* @param qualifier The artifact qualifier
28+
* @param artifactType The artifact type (eg. jar, pom etc)
29+
* @throws IOException
30+
*/
31+
void deploy(String groupId, String artifactId, String version, String qualifier, String artifactType) throws IOException;
32+
33+
/**
34+
* Deploy a Maven artifact
35+
*
36+
* @param groupId The maven group ID
37+
* @param artifactId The maven artifact ID
38+
* @param version The artifact version
39+
* @param qualifier The artifact qualifier
40+
* @throws IOException
41+
*/
42+
void deploy(String groupId, String artifactId, String version, String qualifier) throws IOException;
43+
44+
/**
45+
* Returns the maven repository base (eg. the maven central)
46+
*
47+
* @return The maven repository base
48+
*/
49+
String getRepoBase();
50+
51+
/**
52+
* Returns the j4rs deployment target. ie. the location where the rust application will search to find
53+
* the maven artifacts
54+
*
55+
* @return The deployment target
56+
*/
57+
String getDeployTarget();
58+
}

java/src/main/java/org/astonbitecode/j4rs/api/deploy/SimpleMavenDeployer.java

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@
3333
import org.w3c.dom.Document;
3434
import org.xml.sax.SAXException;
3535

36-
public class SimpleMavenDeployer {
36+
public class SimpleMavenDeployer implements MavenDeployerApi {
3737
private static final String MAVEN_CENTRAL = "https://repo.maven.apache.org/maven2";
38+
private static final String JAR_ARTIFACT_EXTENSION = "jar";
3839
private final String M2_CACHE = System.getProperty("user.home") + File.separator + ".m2" + File.separator
3940
+ "repository";
4041

@@ -61,15 +62,22 @@ public SimpleMavenDeployer(String repoBase, boolean checkLocalCache, String depl
6162
new File(deployTarget).mkdirs();
6263
}
6364

65+
@Override
6466
public void deploy(String groupId, String artifactId, String version, String qualifier) throws IOException {
65-
String jarName = generateArtifactName(artifactId, version, qualifier);
67+
String artifactType = JAR_ARTIFACT_EXTENSION;
68+
deploy(groupId, artifactId, version, qualifier, artifactType);
69+
}
70+
71+
@Override
72+
public void deploy(String groupId, String artifactId, String version, String qualifier, String artifactType) throws IOException {
73+
String jarName = DeployUtils.generateArtifactName(artifactId, version, qualifier, artifactType);
6674
boolean searchRemoteRepo = true;
6775

68-
if (!artifactExists(groupId, artifactId, version, qualifier)) {
76+
if (!DeployUtils.artifactExists(groupId, artifactId, version, qualifier, artifactType, deployTarget)) {
6977
String fullJarDeployPath = deployTarget + File.separator + jarName;
7078
if (checkLocalCache) {
7179
try {
72-
deployFromLocalCache(groupId, artifactId, version, qualifier);
80+
deployFromLocalCache(groupId, artifactId, version, qualifier, artifactType);
7381
searchRemoteRepo = false;
7482
} catch (Exception error) {
7583
/* ignore */
@@ -87,15 +95,9 @@ public void deploy(String groupId, String artifactId, String version, String qua
8795
}
8896
}
8997

90-
private boolean artifactExists(String groupId, String artifactId, String version, String qualifier) {
91-
String jarName = generateArtifactName(artifactId, version, qualifier);
92-
String pathString = deployTarget + File.separator + jarName;
93-
return new File(pathString).exists();
94-
}
95-
96-
void deployFromLocalCache(String groupId, String artifactId, String version, String qualifier)
98+
void deployFromLocalCache(String groupId, String artifactId, String version, String qualifier, String artifactType)
9799
throws MalformedURLException, IOException {
98-
String jarName = generateArtifactName(artifactId, version, qualifier);
100+
String jarName = DeployUtils.generateArtifactName(artifactId, version, qualifier, artifactType);
99101
String pathString = generatePathTagret(M2_CACHE, groupId, artifactId, version, jarName);
100102

101103
ReadableByteChannel readableByteChannel = Channels
@@ -105,15 +107,6 @@ void deployFromLocalCache(String groupId, String artifactId, String version, Str
105107
}
106108
}
107109

108-
String generateArtifactName(String artifactId, String version, String qualifier) {
109-
StringBuilder jarName = new StringBuilder(String.format("%s-%s", artifactId, version));
110-
if (qualifier != null && !qualifier.isEmpty()) {
111-
jarName.append("-").append(qualifier);
112-
}
113-
jarName.append(".jar");
114-
return jarName.toString();
115-
}
116-
117110
String generateUrlTagret(String groupId, String artifactId, String version, String jarName) throws IOException {
118111
if (version.endsWith("-SNAPSHOT")) {
119112
String latestSnapshotJarName = getLatestSnapshotName(groupId, artifactId, version);
@@ -145,8 +138,13 @@ String generatePathTagret(String base, String groupId, String artifactId, String
145138
File.separator, artifactId, File.separator, version, File.separator, jarName);
146139
}
147140

141+
@Override
148142
public String getRepoBase() {
149143
return repoBase;
150144
}
151145

146+
@Override
147+
public String getDeployTarget() {
148+
return deployTarget;
149+
}
152150
}

0 commit comments

Comments
 (0)