Skip to content

Commit

Permalink
Support Jmix 2.x
Browse files Browse the repository at this point in the history
  • Loading branch information
Gavrilov-Ivan committed Dec 5, 2023
1 parent 7ec0585 commit e70dd39
Show file tree
Hide file tree
Showing 25 changed files with 1,231 additions and 35 deletions.
109 changes: 104 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

* resolve all dependencies for Jmix framework
* resolve dependencies for any custom library
* export resolved dependencies in Maven repository format
* export resolved dependencies in Maven repository format (for java) or tgz archives (for npm)
* upload exported dependencies into custom Nexus repository

## Installation
Expand All @@ -26,14 +26,18 @@ deptool
- bin (contains CLI executables)
- lib (required Java libraries)
- resolver (Gradle project used by the tool to resolve dependencies)
- npm-resolver (Gradle project used by the tool to resolve npm dependencies)
```

NOTE: to be able to export npm dependencies you need to install Node.js with npm package manager.

## Usage Scenarios

The general usage scenario is the following:

- Resolve all Jmix dependencies for the specific version of the framework and its add-ons. If commercial add-ons are needed, this step requires the Enterprise subscription key. Resolve also additional libraries required for the project, if any. On this step, the resolved artifacts will be downloaded to the local Gradle cache.
- Export the resolved artifacts to the local Maven repository in the portable format.
- Resolve all Jmix dependencies for the specific version of the framework and its add-ons. If commercial add-ons are needed, this step requires the Enterprise subscription key. Resolve also additional libraries required for the project, if any. On this step, the resolved Java artifacts will be downloaded to the local Gradle cache. Npm dependencies are resolved as package-lock.json file.
- Export the resolved Java artifacts to the local Maven repository in the portable format.
- Export the resolved npm artifacts as a folder with packages as a tgz archives.
- Upload exported artifacts to a custom Nexus repository.
- Configure the project for using the custom Nexus repository.

Expand All @@ -51,7 +55,7 @@ The tool delegates dependencies resolution to a special Gradle project. The flow
The tool can resolve all dependencies required for specific Jmix version.

```shell
./deptool resolve-jmix --jmix-version 1.4.2
./deptool resolve-jmix --jmix-version 2.1.0
```

The command resolves and downloads dependencies required for Jmix starters to the Gradle cache.
Expand All @@ -63,7 +67,8 @@ Command options:
* `--resolve-commercial-addons` - whether to resolve Jmix commercial add-ons. The `--jmix-license-key` option must be provided in this case. By default, only open-source modules dependencies are resolved.
* `--jmix-license-key` - your Jmix license key. This option is required if you resolve Jmix commercial add-ons.
* `--gradle-user-home` - gradle user home directory. It is the directory where dependencies will be downloaded to by Gradle. This directory must distinct from the user home of the gradle installed on your machine in order to contain only dependencies required for Jmix. The default value is `../gradle-home`.
* `--resolver-project` - a path to a spacial gradle project used for dependencies resolution. This project is delivered within the distribution bundle. The default value is `../resolver`.
* `--resolver-project` - a path to a special gradle project used for dependencies resolution. This project is delivered within the distribution bundle. The default value is `../resolver`.
* `--gradle-version` - version of Gradle installation that will be used.

If you run the command from within the `deptool/bin` directory then the only required option is the `--jmix-version`. Other options have default values that work for that case. If you run the command from some other place then you need to configure a location of gradle home and a location of the resolver project.

Expand All @@ -85,10 +90,39 @@ Command options:
* `--resolver-project` - see `resolve-jmix` command documentation.
* `--jmix-license-key` - your Jmix license key. This option is required if you resolve dependencies that use Jmix commercial add-ons.
* `--repository` - additional Maven repository that must be used for dependencies resolution. If no authentication is required then pass repository URL as parameter value. If authentication is required, then pass URL, username and password separated by `|`, e.g. `http://localhost:8081/jmix|admin|admin`
* `--gradle-version` - version of Gradle installation that will be used.


If `--jmix-version` option is defined then Jmix BOM will be used during dependency resolution. Jmix BOM is not used by default.

### Resolve NPM Dependencies (resolve-npm)

The tool can resolve all npm dependencies required for specific Jmix version.

```shell
./deptool resolve-npm --jmix-version 2.1.0
```

The command resolves npm dependencies required for Jmix frontend as package-lock.json.

```
NOTE: save generated package-lock.json file - it's required for further project configuration
```

Command options:

* `--jmix-version` (required) - the Jmix framework version.
* `--jmix-plugin-version` - Jmix plugin version. If not defined the value from the `--jmix-version` will be used.
* `--resolve-commercial-addons` - whether to resolve Jmix commercial add-ons. The `--jmix-license-key` option must be provided in this case. By default, only open-source modules dependencies are resolved.
* `--jmix-license-key` - your Jmix license key. This option is required if you resolve Jmix commercial add-ons.
* `--gradle-user-home` - gradle user home directory. It is the directory where dependencies will be downloaded to by Gradle. This directory must distinct from the user home of the gradle installed on your machine in order to contain only dependencies required for Jmix. The default value is `../gradle-home`.
* `--resolver-project` - a path to a special gradle project used for dependencies resolution. This project is delivered within the distribution bundle. The default value is `../npm-resolver`.
* `--gradle-version` - version of Gradle installation that will be used.

If you run the command from within the `deptool/bin` directory then the only required option is the `--jmix-version`. Other options have default values that work for that case. If you run the command from some other place then you need to configure a location of gradle home and a location of the resolver project.

Keep in mind that each invocation of the `resolve-npm` command will override the result of the previous one.

## Export Resolved Dependencies (export)

The command copies all resolved artifact from the gradle user home directory to the specific target directory. In the target directory files will be organized in a Maven repository format.
Expand All @@ -105,6 +139,23 @@ Command options:
* `--gradle-user-home` - gradle home directory with resolved dependencies. See `resolve-jmix` command documentation.
* `--report-file` - a path to a file which will contain a list of all exported artifacts.

## Export Resolved NPM Dependencies (export-npm)

The command download all npm packages declared in package-lock.json as a folders with tgz archives.
The command copies all resolved artifact from the gradle user home directory to the specific target directory. In the target directory files will be organized in a Maven repository format.

```shell
./deptool export-npm
```

By default, if you run the `deptool` from the `deptool/bin` directory the command will export artifacts to the `deptool/export-npm` directory. If you want to change the output directory location, use the `--target-dir` option.

Command options:

* `--package-lock-file` - path to the package-lock.json file with declared dependencies (`../npm-resolver/package-lock.json` by default).
* `--target-dir` - a directory where dependencies artifacts will be exported to (`../export-npm` by default).
* `--resolver-project` - a path to a special gradle project used for dependencies resolution. This project is delivered within the distribution bundle. The default value is `../npm-resolver`.

## Upload Exported Dependencies to Nexus Repository (upload)

The command uploads artifacts exported by the `export` command to the Nexus repository.
Expand All @@ -125,6 +176,26 @@ Command options:
* `--nexus-password` (required) - Nexus user password.
* `--artifacts-dir` (required) - a directory with artifacts to be uploaded to Nexus.

## Upload Exported NPM Dependencies to Nexus Repository (upload-npm)

The command uploads artifacts exported by the `export-npm` command to the Nexus repository.

```shell
./deptool upload-npm --nexus-url http://localhost:8081 \
--nexus-repository jmix-npm \
--nexus-username admin \
--nexus-password adminpass \
--artifacts-dir ../export-npm
```

Command options:

* `--nexus-url` (required) - Nexus repository URL.
* `--nexus-repository` (required) - Nexus repository name.
* `--nexus-username` (required) - Nexus user username.
* `--nexus-password` (required) - Nexus user password.
* `--artifacts-dir` (required) - a directory with artifacts to be uploaded to Nexus.

## Configure Projects For Working with Custom Nexus Repository

While creating a new Jmix project in Jmix Studio, add custom Nexus repository.
Expand Down Expand Up @@ -163,6 +234,14 @@ Add this instruction to the maven repository configuration in the `build.gradle`

Remove the `mavenCentral()` instruction from the `repositories` section of the `build.gradle` file.

### Configure NPM
Copy previously saved package-lock.json file at hte root of your project.

To use custom npm registry create file `.npmrc` at the root of your project and add there the following line:
`registry=http://localhost:8081/repository/jmix-npm/`

Additional properties (like authentication, etc) can be found in documentation https://docs.npmjs.com/cli/v10/configuring-npm/npmrc


## Install Custom Nexus Repository

Expand Down Expand Up @@ -198,6 +277,9 @@ Fill the **Name** field (e.g. `jmix`) and select the **Version policy**: `Mixed`

![Repositories](images/repository-create.png)

Create NPM repository the same way - use `npm (hosted)` repository type and fill the **Name** (e.g. jmix-npm).


## Implementation Details

### Resolver Project
Expand All @@ -215,6 +297,22 @@ The `JmixDependenciesPlugin` adds the `resolveDependencies` task. This task is i
-PjmixLicenseKey=<your_key>
```

### NPM Resolver Project

The `deptool` utility resolves dependencies by invocation of gradle tasks in the `npm-resolver` project that is packed into the distribution. This project contains a simple predefined `build.gradle` file. In this file Jmix plugin and Jmix BOM are enabled if corresponding project properties are defined (`-PjmixVersion` and `-PjmixPluginVersion`). The `build.gradle` also applies a special gradle plugin that contains tasks that do the resolution.

The `JmixNpmDependenciesPlugin` adds the `resolveNpmDependencies` task. This task is invoked by the `deptool` utility for dependencies resolution.

```shell
./gradlew resolveDependencies \
--dependency io.jmix.flowui:jmix-flowui \
--repository "http://some-external-repo.com:8081/repo|user|password" \
-PjmixVersion=2.1.0 \
-PjmixPluginVersion=2.1.0 \
-PjmixLicenseKey=<your_key>
```
Task configure project using provided data, resolve java dependencies and use Vaadin plugin (vaadinBuildFrontend task) to generate package-lock.json


## Building the Distribution

Expand All @@ -241,4 +339,5 @@ deptool
- bin (contains CLI executables)
- lib (required Java libraries)
- resolver (Gradle project used by the tool to resolve dependencies)
- npm-resolver (Gradle project used by the tool to resolve npm dependencies)
```
9 changes: 8 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
def deptoolDir = "deptool-${project.property('version')}"

tasks.register('prepareDist', Copy) {
dependsOn tasks.cleanBuild, tasks.copyDeptool, tasks.copyResolver
dependsOn tasks.cleanBuild, tasks.copyDeptool, tasks.copyResolver, tasks.copyNpmResolver
}

tasks.register('zipDist', Zip) {
Expand All @@ -24,6 +24,13 @@ tasks.register('copyResolver', Copy) {
into project.layout.buildDirectory.dir("install/${deptoolDir}/resolver")
}

tasks.register('copyNpmResolver', Copy) {
from (project.layout.projectDirectory.dir('npm-resolver')) {
exclude '**/build', '**/.gradle'
}
into project.layout.buildDirectory.dir("install/${deptoolDir}/npm-resolver")
}

tasks.register('cleanBuild', Delete) {
delete project.buildDir
}
3 changes: 3 additions & 0 deletions deptool/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ repositories {
dependencies {
implementation "org.gradle:gradle-tooling-api:7.6"
implementation 'com.beust:jcommander:1.82'
implementation 'commons-io:commons-io:2.15.0'
implementation 'org.slf4j:slf4j-api:2.0.5'
implementation 'ch.qos.logback:logback-core:1.4.5'
implementation 'ch.qos.logback:logback-classic:1.4.5'
Expand All @@ -23,6 +24,8 @@ dependencies {
exclude group: "pull-parser", module: "pull-parser"
}

implementation 'com.google.code.gson:gson:2.10.1'

testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}
Expand Down
9 changes: 3 additions & 6 deletions deptool/src/main/java/io/jmix/dependency/cli/CliRunner.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
package io.jmix.dependency.cli;

import com.beust.jcommander.IDefaultProvider;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.ParameterException;
import io.jmix.dependency.cli.command.*;
import io.jmix.dependency.cli.dependency.JmixDependencies;
import io.jmix.dependency.cli.gradle.JmixGradleClient;
import org.gradle.tooling.ProjectConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CliRunner {
Expand All @@ -22,9 +16,12 @@ public class CliRunner {
public static void main(String[] args) {
Map<String, BaseCommand> commands = new HashMap<>();
commands.put("resolve-jmix", new ResolveJmixCommand());
commands.put("resolve-npm", new ResolveNpmCommand());
commands.put("resolve-lib", new ResolveLibCommand());
commands.put("export", new ExportCommand());
commands.put("export-npm", new ExportNpmCommand());
commands.put("upload", new UploadCommand());
commands.put("upload-npm", new UploadNpmCommand());
JCommander.Builder commanderBuilder = JCommander.newBuilder();
for (Map.Entry<String, BaseCommand> entry : commands.entrySet()) {
commanderBuilder.addCommand(entry.getKey(), entry.getValue());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,15 @@ public static String getDefaultResolverProjectPath() {
return Paths.get(System.getProperty("user.dir")).resolve("../resolver").toAbsolutePath().toString();
}

public static String getDefaultNpmResolverProjectPath() {
return Paths.get(System.getProperty("user.dir")).resolve("../npm-resolver").toAbsolutePath().toString();
}

public static String getDefaultExportDir() {
return Paths.get(System.getProperty("user.dir")).resolve("../export").toAbsolutePath().toString();
}

public static String getDefaultExportNpmDir() {
return Paths.get(System.getProperty("user.dir")).resolve("../export-npm").toAbsolutePath().toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package io.jmix.dependency.cli.command;

import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

@Parameters(commandDescription = "Exports resolved dependencies")
public class ExportNpmCommand implements BaseCommand {

private static final Logger log = LoggerFactory.getLogger(ExportNpmCommand.class);

@Parameter(names = {"--package-lock-file"}, description = "Location of package-lock.json file with npm dependencies", order = 0)
private String packageLockJson;

@Parameter(names = {"--target-dir"}, description = "Export target directory", order = 1)
private String targetDirectory;

@Parameter(names = {"--resolver-project"}, description = "Path to dependencies resolver project", order = 2)
private String resolverProjectPath;

private String downloaderDir;

@Override
public void run() {
if (targetDirectory == null) {
targetDirectory = DefaultPaths.getDefaultExportNpmDir();
}
if (resolverProjectPath == null) {
resolverProjectPath = DefaultPaths.getDefaultNpmResolverProjectPath();
}
if (packageLockJson == null) {
packageLockJson = resolverProjectPath + "/package-lock.json";
}

downloaderDir = resolverProjectPath + "/downloader";

Path targetDirectoryPath = Paths.get(targetDirectory).toAbsolutePath().normalize();
Path packageLockJsonPath = Paths.get(packageLockJson).toAbsolutePath().normalize();

log.info("Target directory: {}", targetDirectoryPath);
log.info("package-lock.json file: {}", packageLockJsonPath);

installNodeTgzDownloader();
downloadNpmArchives(packageLockJsonPath.toString(), targetDirectoryPath.toString());
copyPackageLock();

log.info("Export completed successfully");
}

protected void installNodeTgzDownloader() {
log.info("-= Install node-tgz-downloader =-");
executeCommand(downloaderDir, "npm install node-tgz-downloader");
}

protected void downloadNpmArchives(String packageLockFileLocation, String directory) {
log.info("-= Download tgz-archives =-");
String command = "npx download-tgz package-lock " + packageLockFileLocation + " --directory=" + directory;
executeCommand(downloaderDir, command);
}

protected void copyPackageLock() {
log.info("-= Copy package-lock.json =-");
File source = new File(packageLockJson);
File target = new File(targetDirectory);
try {
FileUtils.copyFileToDirectory(source, target);
} catch (IOException e) {
throw new RuntimeException("Unable to copy package-lock.json", e);
}
}

protected void executeCommand(String workingDirectory, String command) {
boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows");
ProcessBuilder builder = new ProcessBuilder();
if (isWindows) {
builder.command("cmd.exe", "/c", command);
} else {
builder.command("sh", "-c", command);
}

File workingDirectoryFile = Paths.get(workingDirectory).toAbsolutePath().normalize().toFile();
String commandString = String.join(" ", builder.command());
log.info("Working directory: {}, command: {}", workingDirectoryFile, commandString);

builder.directory(workingDirectoryFile);

builder.inheritIO();
Process process;
try {
process = builder.start();
} catch (IOException e) {
throw new RuntimeException("Command failed", e);
}

int exitCode;
try {
exitCode = process.waitFor();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (exitCode != 0) {
throw new RuntimeException("Exit code = " + exitCode);
}
}
}

0 comments on commit e70dd39

Please sign in to comment.