Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 59 additions & 4 deletions src/main/java/org/codejive/jpm/Jpm.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
import org.codejive.jpm.json.AppInfo;
import org.codejive.jpm.config.AppInfo;
import org.codejive.jpm.util.*;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.resolution.DependencyResolutionException;
Expand Down Expand Up @@ -91,7 +91,22 @@ public Jpm build() {
*/
public SyncStats copy(String[] artifactNames, boolean sync)
throws IOException, DependencyResolutionException {
List<Path> files = Resolver.create(artifactNames).resolvePaths();
return copy(artifactNames, Collections.emptyMap(), sync);
}

/**
* Copies the given artifacts to the target directory.
*
* @param artifactNames The artifacts to copy.
* @param repos A map of additional repository names to URLs where artifacts can be found.
* @param sync Whether to sync the target directory or not.
* @return An instance of {@link SyncStats} containing the statistics of the copy operation.
* @throws IOException If an error occurred during the copy operation.
* @throws DependencyResolutionException If an error occurred during the dependency resolution.
*/
public SyncStats copy(String[] artifactNames, Map<String, String> repos, boolean sync)
throws IOException, DependencyResolutionException {
List<Path> files = Resolver.create(artifactNames, repos).resolvePaths();
return FileUtils.syncArtifacts(files, directory, noLinks, !sync);
}

Expand Down Expand Up @@ -131,10 +146,28 @@ private static String artifactGav(Artifact artifact) {
*/
public SyncStats install(String[] artifactNames)
throws IOException, DependencyResolutionException {
return install(artifactNames, Collections.emptyMap());
}

/**
* Installs the given artifacts to the target directory while also registering them as
* dependencies in the app.yml file in the current directory. If no artifacts are given, all
* dependencies in the app.yml file will be installed. NB: "installation" in this context
* basically means sync-copying the artifacts to the target directory.
*
* @param artifactNames The artifacts to install.
* @param extraRepos A map of additional repository names to URLs where artifacts can be found.
* @return An instance of {@link SyncStats} containing the statistics of the install operation.
* @throws IOException If an error occurred during the install operation.
* @throws DependencyResolutionException If an error occurred during the dependency resolution.
*/
public SyncStats install(String[] artifactNames, Map<String, String> extraRepos)
throws IOException, DependencyResolutionException {
AppInfo appInfo = AppInfo.read();
String[] artifacts = getArtifacts(artifactNames, appInfo);
Map<String, String> repos = getRepositories(extraRepos, appInfo);
if (artifacts.length > 0) {
List<Path> files = Resolver.create(artifacts).resolvePaths();
List<Path> files = Resolver.create(artifacts, repos).resolvePaths();
SyncStats stats = FileUtils.syncArtifacts(files, directory, noLinks, true);
if (artifactNames.length > 0) {
for (String dep : artifactNames) {
Expand Down Expand Up @@ -162,10 +195,26 @@ public SyncStats install(String[] artifactNames)
*/
public List<Path> path(String[] artifactNames)
throws DependencyResolutionException, IOException {
return path(artifactNames, Collections.emptyMap());
}

/**
* Returns the paths of the given artifacts. If no artifacts are given, the paths for all
* dependencies in the app.yml file will be returned instead.
*
* @param artifactNames The artifacts to get the paths for.
* @param extraRepos A map of additional repository names to URLs where artifacts can be found.
* @return A list of paths.
* @throws DependencyResolutionException If an error occurred during the dependency resolution.
* @throws IOException If an error occurred during the operation.
*/
public List<Path> path(String[] artifactNames, Map<String, String> extraRepos)
throws DependencyResolutionException, IOException {
AppInfo appInfo = AppInfo.read();
String[] deps = getArtifacts(artifactNames, appInfo);
Map<String, String> repos = getRepositories(extraRepos, appInfo);
if (deps.length > 0) {
return Resolver.create(deps).resolvePaths();
return Resolver.create(deps, repos).resolvePaths();
} else {
return Collections.emptyList();
}
Expand All @@ -181,6 +230,12 @@ private static String[] getArtifacts(String[] artifactNames, AppInfo appInfo) {
return deps;
}

private Map<String, String> getRepositories(Map<String, String> extraRepos, AppInfo appInfo) {
Map<String, String> repos = new HashMap<>(appInfo.repositories);
repos.putAll(extraRepos);
return repos;
}

/**
* Executes an action defined in app.yml file.
*
Expand Down
51 changes: 43 additions & 8 deletions src/main/java/org/codejive/jpm/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
//DEPS org.yaml:snakeyaml:2.4
//DEPS org.jline:jline-console-ui:3.30.5 org.jline:jline-terminal-jni:3.30.5
//DEPS org.slf4j:slf4j-api:2.0.17 org.slf4j:slf4j-simple:2.0.17
//SOURCES Jpm.java json/AppInfo.java util/FileUtils.java util/Resolver.java util/ScriptUtils.java
//SOURCES Jpm.java config/AppInfo.java util/FileUtils.java util/Resolver.java util/ScriptUtils.java
//SOURCES util/SearchResult.java util/SearchUtils.java util/SyncStats.java util/Version.java
// spotless:on

package org.codejive.jpm;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.Callable;
Expand Down Expand Up @@ -80,7 +82,10 @@ public Integer call() throws Exception {
.directory(artifactsMixin.depsMixin.directory)
.noLinks(artifactsMixin.depsMixin.noLinks)
.build()
.copy(artifactsMixin.artifactNames, sync);
.copy(
artifactsMixin.artifactNames,
artifactsMixin.getRepositoryMap(),
sync);
if (!quietMixin.quiet) {
printStats(stats);
}
Expand Down Expand Up @@ -280,7 +285,9 @@ public Integer call() throws Exception {
.directory(optionalArtifactsMixin.depsMixin.directory)
.noLinks(optionalArtifactsMixin.depsMixin.noLinks)
.build()
.install(optionalArtifactsMixin.artifactNames);
.install(
optionalArtifactsMixin.artifactNames,
optionalArtifactsMixin.getRepositoryMap());
if (!quietMixin.quiet) {
printStats(stats);
}
Expand All @@ -305,7 +312,9 @@ public Integer call() throws Exception {
.directory(optionalArtifactsMixin.depsMixin.directory)
.noLinks(optionalArtifactsMixin.depsMixin.noLinks)
.build()
.path(optionalArtifactsMixin.artifactNames);
.path(
optionalArtifactsMixin.artifactNames,
optionalArtifactsMixin.getRepositoryMap());
if (!files.isEmpty()) {
String classpath =
files.stream()
Expand Down Expand Up @@ -492,9 +501,37 @@ static class DepsMixin {
boolean noLinks;
}

static class ArtifactsMixin {
static class BaseArtifactsMixin {
@Mixin DepsMixin depsMixin;

@Option(
names = {"-r", "--repo"},
description =
"URL to additional repository to use when resolving artifacts. Can be preceded by a name and an equals sign, e.g. -r myrepo=https://my.repo.com/maven2. When needing to pass user and password you can set JPM_REPO_<name>_USER and JPM_REPO_<name>_PASSWORD environment variables.")
List<String> repositories = new ArrayList<>();

Map<String, String> getRepositoryMap() {
Map<String, String> repoMap = new HashMap<>();
for (String repo : repositories) {
int eq = repo.indexOf('=');
if (eq > 0) {
repoMap.put(repo.substring(0, eq), repo.substring(eq + 1));
} else {
String name = repo;
try {
URL url = new URL(repo);
name = url.getHost();
} catch (MalformedURLException e) {
// Ignore
}
repoMap.put(name, repo);
}
}
return repoMap;
}
}

static class ArtifactsMixin extends BaseArtifactsMixin {
@Parameters(
paramLabel = "artifacts",
description =
Expand All @@ -503,9 +540,7 @@ static class ArtifactsMixin {
private String[] artifactNames = {};
}

static class OptionalArtifactsMixin {
@Mixin DepsMixin depsMixin;

static class OptionalArtifactsMixin extends BaseArtifactsMixin {
@Parameters(
paramLabel = "artifacts",
description =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.codejive.jpm.json;
package org.codejive.jpm.config;

import java.io.IOException;
import java.io.Reader;
Expand All @@ -17,7 +17,9 @@
*/
public class AppInfo {
private Map<String, Object> yaml = new TreeMap<>();

public Map<String, String> dependencies = new TreeMap<>();
public Map<String, String> repositories = new TreeMap<>();
public Map<String, String> actions = new TreeMap<>();

/** The official name of the app.yml file. */
Expand All @@ -41,7 +43,7 @@ public String[] getDependencyGAVs() {
* @return The action command or null if not found
*/
public String getAction(String actionName) {
return actions.get(actionName);
return actions != null ? actions.get(actionName) : null;
}

/**
Expand All @@ -50,7 +52,7 @@ public String getAction(String actionName) {
* @return A set of action names
*/
public java.util.Set<String> getActionNames() {
return actions.keySet();
return actions != null ? actions.keySet() : java.util.Collections.emptySet();
}

/**
Expand Down Expand Up @@ -80,6 +82,14 @@ public static AppInfo read() throws IOException {
appInfo.dependencies.put(entry.getKey(), entry.getValue().toString());
}
}
// Parse repositories section
if (appInfo.yaml.containsKey("repositories")
&& appInfo.yaml.get("repositories") instanceof Map) {
Map<String, Object> deps = (Map<String, Object>) appInfo.yaml.get("repositories");
for (Map.Entry<String, Object> entry : deps.entrySet()) {
appInfo.repositories.put(entry.getKey(), entry.getValue().toString());
}
}
// Parse actions section
if (appInfo.yaml.containsKey("actions") && appInfo.yaml.get("actions") instanceof Map) {
Map<String, Object> actions = (Map<String, Object>) appInfo.yaml.get("actions");
Expand All @@ -105,7 +115,16 @@ public static void write(AppInfo appInfo) throws IOException {
Yaml yaml = new Yaml(dopts);
// WARNING awful code ahead
appInfo.yaml.put("dependencies", (Map<String, Object>) (Map) appInfo.dependencies);
appInfo.yaml.put("actions", (Map<String, Object>) (Map) appInfo.actions);
if (!appInfo.repositories.isEmpty()) {
appInfo.yaml.put("repositories", (Map<String, Object>) (Map) appInfo.repositories);
} else {
appInfo.yaml.remove("repositories");
}
if (!appInfo.actions.isEmpty()) {
appInfo.yaml.put("actions", (Map<String, Object>) (Map) appInfo.actions);
} else {
appInfo.yaml.remove("actions");
}
yaml.dump(appInfo.yaml, out);
}
}
Expand Down
43 changes: 26 additions & 17 deletions src/main/java/org/codejive/jpm/util/Resolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
import eu.maveniverse.maven.mima.context.Runtimes;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.DependencyRequest;
import org.eclipse.aether.resolution.DependencyResolutionException;
Expand All @@ -20,20 +23,22 @@

public class Resolver {
private final List<Artifact> artifacts;
private final List<RemoteRepository> repositories;

private List<ArtifactResult> resolvedArtifacts;

public static Resolver create(String[] artifactNames) {
return new Resolver(artifactNames);
public static Resolver create(String[] artifactNames, Map<String, String> repositories) {
return new Resolver(artifactNames, repositories);
}

private Resolver(String[] artifactNames) {
private Resolver(String[] artifactNames, Map<String, String> repos) {
artifacts = parseArtifacts(artifactNames);
repositories = parseRepositories(repos);
}

public List<ArtifactResult> resolve() throws DependencyResolutionException {
if (resolvedArtifacts == null) {
resolvedArtifacts = resolveArtifacts(artifacts);
resolvedArtifacts = resolveArtifacts(artifacts, repositories);
}
return resolvedArtifacts;
}
Expand All @@ -45,31 +50,22 @@ public List<Path> resolvePaths() throws DependencyResolutionException {
.collect(Collectors.toList());
}

/**
* Parses the given artifact names into a list of {@link Artifact} instances.
*
* @param artifactNames the artifact names to parse as an array of strings in the format
* "groupId:artifactId:version"
* @return a list of {@link Artifact} instances
*/
public static List<Artifact> parseArtifacts(String[] artifactNames) {
return Arrays.stream(artifactNames).map(DefaultArtifact::new).collect(Collectors.toList());
}

/**
* Resolves the given artifacts.
*
* @param artifacts the artifacts to resolve as a list of {@link Artifact} instances
* @return the resolved artifacts as a list of {@link ArtifactResult} instances
* @throws DependencyResolutionException if an error occurs while resolving the artifacts
*/
public static List<ArtifactResult> resolveArtifacts(List<Artifact> artifacts)
public static List<ArtifactResult> resolveArtifacts(
List<Artifact> artifacts, List<RemoteRepository> repositories)
throws DependencyResolutionException {
List<Dependency> dependencies =
artifacts.stream()
.map(a -> new Dependency(a, JavaScopes.RUNTIME))
.collect(Collectors.toList());
ContextOverrides overrides = ContextOverrides.create().build();
ContextOverrides overrides =
ContextOverrides.create().withUserSettings(true).repositories(repositories).build();
Runtime runtime = Runtimes.INSTANCE.getRuntime();
try (Context context = runtime.create(overrides)) {
CollectRequest collectRequest =
Expand All @@ -86,4 +82,17 @@ public static List<ArtifactResult> resolveArtifacts(List<Artifact> artifacts)
return dependencyResult.getArtifactResults();
}
}

private static List<Artifact> parseArtifacts(String[] artifactNames) {
return Arrays.stream(artifactNames).map(DefaultArtifact::new).collect(Collectors.toList());
}

private static List<RemoteRepository> parseRepositories(Map<String, String> repositories) {
if (repositories == null) {
return Collections.emptyList();
}
return repositories.entrySet().stream()
.map(e -> new RemoteRepository.Builder(e.getKey(), "default", e.getValue()).build())
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.codejive.jpm.json;
package org.codejive.jpm.config;

import static org.assertj.core.api.Assertions.*;

Expand Down
Loading