From 15f03b358f7fa401f962241f53df8ac200747dda Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Wed, 14 Sep 2022 09:09:25 +0100 Subject: [PATCH] Rework getting started section - With build add feature to resolve dep versions which can be used in docs. - More polished maven/gradle example. - Relates #527 --- .../shell/gradle/DocsPlugin.java | 38 ++++- .../gradle/ExtractVersionConstraints.java | 154 ++++++++++++++++++ .../code/getting-started-run-interactive.out | 27 +++ .../getting-started-run-noninteractive.out | 26 +++ .../src/main/asciidoc/getting-started.adoc | 116 ++++++++----- .../src/main/asciidoc/index.adoc | 2 +- .../src/main/asciidoc/introduction.adoc | 9 - 7 files changed, 314 insertions(+), 58 deletions(-) create mode 100644 buildSrc/src/main/java/org/springframework/shell/gradle/ExtractVersionConstraints.java create mode 100644 spring-shell-docs/src/main/asciidoc/code/getting-started-run-interactive.out create mode 100644 spring-shell-docs/src/main/asciidoc/code/getting-started-run-noninteractive.out diff --git a/buildSrc/src/main/java/org/springframework/shell/gradle/DocsPlugin.java b/buildSrc/src/main/java/org/springframework/shell/gradle/DocsPlugin.java index 37c742ce..b12432c3 100644 --- a/buildSrc/src/main/java/org/springframework/shell/gradle/DocsPlugin.java +++ b/buildSrc/src/main/java/org/springframework/shell/gradle/DocsPlugin.java @@ -33,6 +33,7 @@ import org.asciidoctor.gradle.jvm.AsciidoctorTask; import org.gradle.api.Action; import org.gradle.api.Plugin; import org.gradle.api.Project; +import org.gradle.api.Task; import org.gradle.api.plugins.JavaLibraryPlugin; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.PluginManager; @@ -57,20 +58,26 @@ class DocsPlugin implements Plugin { pluginManager.apply(SpringMavenPlugin.class); pluginManager.apply(AsciidoctorJPlugin.class); - configureAdocPlugins(project); + ExtractVersionConstraints dependencyVersions = project.getTasks().create("dependencyVersions", + ExtractVersionConstraints.class, task -> { + task.enforcedPlatform(":spring-shell-management"); + }); + + configureAdocPlugins(project, dependencyVersions); project.getTasks().withType(GenerateModuleMetadata.class, metadata -> { metadata.setEnabled(false); }); } - private void configureAdocPlugins(Project project) { + private void configureAdocPlugins(Project project, ExtractVersionConstraints dependencyVersions) { project.getPlugins().withType(AsciidoctorJPlugin.class, (asciidoctorPlugin) -> { createDefaultAsciidoctorRepository(project); makeAllWarningsFatal(project); Sync unzipResources = createUnzipDocumentationResourcesTask(project); Sync snippetsResources = createSnippetsResourcesTask(project); project.getTasks().withType(AbstractAsciidoctorTask.class, (asciidoctorTask) -> { + asciidoctorTask.dependsOn(dependencyVersions); asciidoctorTask.dependsOn(unzipResources); asciidoctorTask.dependsOn(snippetsResources); // configureExtensions(project, asciidoctorTask); @@ -94,12 +101,13 @@ class DocsPlugin implements Plugin { // For now copy the entire sourceDir over so that include files are // available in the intermediateWorkDir resourcesSrcDirSpec.include("images/*"); + resourcesSrcDirSpec.include("code/*"); } }); } }); if (asciidoctorTask instanceof AsciidoctorTask) { - configureHtmlOnlyAttributes(project, asciidoctorTask); + configureHtmlOnlyAttributes(project, asciidoctorTask, dependencyVersions); } }); }); @@ -179,7 +187,26 @@ class DocsPlugin implements Plugin { asciidoctorTask.options(Collections.singletonMap("doctype", "book")); } - private void configureHtmlOnlyAttributes(Project project, AbstractAsciidoctorTask asciidoctorTask) { + private void configureHtmlOnlyAttributes(Project project, AbstractAsciidoctorTask asciidoctorTask, + ExtractVersionConstraints dependencyVersions) { + + asciidoctorTask.doFirst(new Action() { + + @Override + public void execute(Task arg0) { + asciidoctorTask.getAttributeProviders().add(new AsciidoctorAttributeProvider() { + @Override + public Map getAttributes() { + Map versionConstraints = dependencyVersions.getVersionConstraints(); + Map attrs = new HashMap<>(); + attrs.put("spring-version", versionConstraints.get("org.springframework:spring-core")); + attrs.put("spring-boot-version", versionConstraints.get("org.springframework.boot:spring-boot")); + return attrs; + } + }); + } + }); + Map attributes = new HashMap<>(); attributes.put("toc", "left"); attributes.put("source-highlighter", "highlight.js"); @@ -195,8 +222,7 @@ class DocsPlugin implements Plugin { Object version = project.getVersion(); Map attrs = new HashMap<>(); if (version != null && version.toString() != Project.DEFAULT_VERSION) { - attrs.put("revnumber", version); - attrs.put("projectVersion", version); + attrs.put("project-version", version); } return attrs; } diff --git a/buildSrc/src/main/java/org/springframework/shell/gradle/ExtractVersionConstraints.java b/buildSrc/src/main/java/org/springframework/shell/gradle/ExtractVersionConstraints.java new file mode 100644 index 00000000..fb3201dc --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/shell/gradle/ExtractVersionConstraints.java @@ -0,0 +1,154 @@ +package org.springframework.shell.gradle; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +import org.gradle.api.DefaultTask; +import org.gradle.api.artifacts.ComponentMetadataDetails; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.DependencyConstraint; +import org.gradle.api.artifacts.DependencyConstraintMetadata; +import org.gradle.api.artifacts.dsl.DependencyHandler; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.TaskAction; + +/** + * @author Janne Valkealahti + */ +class ExtractVersionConstraints extends DefaultTask { + + private final Configuration configuration; + + private final Map versionConstraints = new TreeMap<>(); + + private final Set constrainedVersions = new TreeSet<>(); + + private final Set versionProperties = new TreeSet<>(); + + private final List projectPaths = new ArrayList<>(); + + public ExtractVersionConstraints() { + DependencyHandler dependencies = getProject().getDependencies(); + this.configuration = getProject().getConfigurations().create(getName()); + dependencies.getComponents().all(this::processMetadataDetails); + } + + public void enforcedPlatform(String projectPath) { + this.configuration.getDependencies().add(getProject().getDependencies().enforcedPlatform( + getProject().getDependencies().project(Collections.singletonMap("path", projectPath)))); + this.projectPaths.add(projectPath); + } + + @Internal + public Map getVersionConstraints() { + return Collections.unmodifiableMap(this.versionConstraints); + } + + @Internal + public Set getConstrainedVersions() { + return this.constrainedVersions; + } + + @Internal + public Set getVersionProperties() { + return this.versionProperties; + } + + @TaskAction + void extractVersionConstraints() { + this.configuration.resolve(); + for (String projectPath : this.projectPaths) { + for (DependencyConstraint constraint : getProject().project(projectPath).getConfigurations() + .getByName("apiElements").getAllDependencyConstraints()) { + this.versionConstraints.put(constraint.getGroup() + ":" + constraint.getName(), + constraint.getVersionConstraint().toString()); + this.constrainedVersions.add(new ConstrainedVersion(constraint.getGroup(), constraint.getName(), + constraint.getVersionConstraint().toString())); + } + } + } + + private void processMetadataDetails(ComponentMetadataDetails details) { + details.allVariants((variantMetadata) -> variantMetadata.withDependencyConstraints((dependencyConstraints) -> { + for (DependencyConstraintMetadata constraint : dependencyConstraints) { + this.versionConstraints.put(constraint.getGroup() + ":" + constraint.getName(), + constraint.getVersionConstraint().toString()); + this.constrainedVersions.add(new ConstrainedVersion(constraint.getGroup(), constraint.getName(), + constraint.getVersionConstraint().toString())); + } + })); + } + + public static final class ConstrainedVersion implements Comparable, Serializable { + + private final String group; + + private final String artifact; + + private final String version; + + private ConstrainedVersion(String group, String artifact, String version) { + this.group = group; + this.artifact = artifact; + this.version = version; + } + + public String getGroup() { + return this.group; + } + + public String getArtifact() { + return this.artifact; + } + + public String getVersion() { + return this.version; + } + + @Override + public int compareTo(ConstrainedVersion other) { + int groupComparison = this.group.compareTo(other.group); + if (groupComparison != 0) { + return groupComparison; + } + return this.artifact.compareTo(other.artifact); + } + + } + + public static final class VersionProperty implements Comparable, Serializable { + + private final String libraryName; + + private final String versionProperty; + + public VersionProperty(String libraryName, String versionProperty) { + this.libraryName = libraryName; + this.versionProperty = versionProperty; + } + + public String getLibraryName() { + return this.libraryName; + } + + public String getVersionProperty() { + return this.versionProperty; + } + + @Override + public int compareTo(VersionProperty other) { + int groupComparison = this.libraryName.compareToIgnoreCase(other.libraryName); + if (groupComparison != 0) { + return groupComparison; + } + return this.versionProperty.compareTo(other.versionProperty); + } + + } +} diff --git a/spring-shell-docs/src/main/asciidoc/code/getting-started-run-interactive.out b/spring-shell-docs/src/main/asciidoc/code/getting-started-run-interactive.out new file mode 100644 index 00000000..57fbc5ab --- /dev/null +++ b/spring-shell-docs/src/main/asciidoc/code/getting-started-run-interactive.out @@ -0,0 +1,27 @@ +$ $JAVA_HOME/bin/java -jar demo-0.0.1-SNAPSHOT.jar + + . ____ _ __ _ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ +( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ + \\/ ___)| |_)| | | | | || (_| | ) ) ) ) + ' |____| .__|_| |_|_| |_\__, | / / / / + =========|_|==============|___/=/_/_/_/ + :: Spring Boot :: (v{spring-boot-version}) + +2022-09-13T18:42:12.818+01:00 INFO 12644 --- [ main] com.example.demo.DemoApplication +: Starting DemoApplication using Java 17.0.4 on ... +2022-09-13T18:42:12.821+01:00 INFO 12644 --- [ main] com.example.demo.DemoApplication +: No active profile set, falling back to 1 default profile: "default" +2022-09-13T18:42:13.606+01:00 INFO 12644 --- [ main] com.example.demo.DemoApplication +: Started DemoApplication in 1.145 seconds (process running for 1.578) +shell:>help +AVAILABLE COMMANDS + +Built-In Commands + help: Display help about available commands + stacktrace: Display the full stacktrace of the last error. + clear: Clear the shell screen. + quit, exit: Exit the shell. + history: Display or save the history of previously run commands + version: Show version info + script: Read and execute commands from a file. diff --git a/spring-shell-docs/src/main/asciidoc/code/getting-started-run-noninteractive.out b/spring-shell-docs/src/main/asciidoc/code/getting-started-run-noninteractive.out new file mode 100644 index 00000000..9aa1b8dd --- /dev/null +++ b/spring-shell-docs/src/main/asciidoc/code/getting-started-run-noninteractive.out @@ -0,0 +1,26 @@ +$JAVA_HOME/bin/java -jar demo-0.0.1-SNAPSHOT.jar help + + . ____ _ __ _ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ +( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ + \\/ ___)| |_)| | | | | || (_| | ) ) ) ) + ' |____| .__|_| |_|_| |_\__, | / / / / + =========|_|==============|___/=/_/_/_/ + :: Spring Boot :: (v{spring-boot-version}) + +2022-09-13T18:42:12.818+01:00 INFO 12644 --- [ main] com.example.demo.DemoApplication +: Starting DemoApplication using Java 17.0.4 on ... +2022-09-13T18:42:12.821+01:00 INFO 12644 --- [ main] com.example.demo.DemoApplication +: No active profile set, falling back to 1 default profile: "default" +2022-09-13T18:42:13.606+01:00 INFO 12644 --- [ main] com.example.demo.DemoApplication +: Started DemoApplication in 1.145 seconds (process running for 1.578) +AVAILABLE COMMANDS + +Built-In Commands + help: Display help about available commands + stacktrace: Display the full stacktrace of the last error. + clear: Clear the shell screen. + quit, exit: Exit the shell. + history: Display or save the history of previously run commands + version: Show version info + script: Read and execute commands from a file. diff --git a/spring-shell-docs/src/main/asciidoc/getting-started.adoc b/spring-shell-docs/src/main/asciidoc/getting-started.adoc index 9b82c992..bb39f3c7 100644 --- a/spring-shell-docs/src/main/asciidoc/getting-started.adoc +++ b/spring-shell-docs/src/main/asciidoc/getting-started.adoc @@ -2,49 +2,95 @@ To see what Spring Shell has to offer, we can write a trivial shell application that has a simple command to add two numbers. -=== Writing a Simple Boot Application +IMPORTANT: _Spring Shell_ is based on _Spring Boot_ {spring-boot-version} and +_Spring Framework_ {spring-version} and thus requires _JDK 17_. -Starting with version 2, Spring Shell has been rewritten from the ground up with various -enhancements in mind, one of which is easy integration with Spring Boot. +=== Writing a Simple Shell Application For the purpose of this tutorial, we create a simple Spring Boot application by -using https://start.spring.io. This minimal application depends only on `spring-boot-starter` -and configures the `spring-boot-maven-plugin` to generate an executable über-jar: +using https://start.spring.io where you can choose _Spring Shell_ dependency. +This minimal application depends only on `spring-boot-starter` and +`spring-shell-starter`. + +NOTE: _Spring Shell_ version on `start.spring.io` is usually latest release. + +With _maven_ you're expected to have something like: ==== -[source, xml] +[source, xml, subs=attributes+] ---- + + {project-version} + + org.springframework.boot spring-boot-starter + + org.springframework.boot + spring-shell-starter + + + + + + org.springframework.shell + spring-shell-dependencies + ${spring-shell.version} + pom + import + + + ---- ==== -[[using-spring-shell-add-dependency]] -=== Adding a Dependency on Spring Shell - -The easiest way to get going with Spring Shell is to depend on the `{starter-artifactId}` artifact. -This comes with everything you need to use Spring Shell and plays nicely with Boot, -configuring only the necessary beans as needed: +With _gradle_ you're expected to have something like: ==== -[source, xml, subs=attributes+] +[source, groovy, subs=attributes+] ---- - - org.springframework.shell - {spring-shell-starter} - {project-version} - +dependencies { + implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.springframework.shell:spring-shell-starter' + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +dependencyManagement { + imports { + mavenBom "org.springframework.shell:spring-shell-dependencies:{project-version}" + } +} ---- ==== -CAUTION: Given that Spring Shell starts the REPL (Read-Eval-Print-Loop) because this dependency is present, -you need to either skip tests when you build (`-DskipTests`) throughout this tutorial or remove the sample integration test -that was generated by https://start.spring.io. If you do not remove it, the integration test creates -the Spring `ApplicationContext` and, depending on your build tool, stays stuck in the eval loop or crashes with a NPE. +CAUTION: Given that Spring Shell starts the REPL (Read-Eval-Print-Loop) because this +dependency is present, you need to either skip tests when you build (`-DskipTests`) +throughout this tutorial or remove the sample integration test that was generated +by https://start.spring.io. If you do not remove it, the integration test creates +the Spring `ApplicationContext` and, depending on your build tool, stays stuck in +the eval loop or crashes with a NPE. + +Once compiled it can be run either in interactive mode: + +==== +[source, text, subs=attributes+] +---- +include::code/getting-started-run-interactive.out[] +---- +==== + +Or in non-interactive mode: + +==== +[source, text, subs=attributes+] +---- +include::code/getting-started-run-noninteractive.out[] +---- +==== [[using-spring-shell-your-first-command]] === Your First Command @@ -76,38 +122,24 @@ public class MyCommands { ---- ==== -[[using-spring-shell-try-application]] -=== Trying the Application - -To build the application and run the generated jar, run the following command: +New _add_ command becomes visible to _help_: ==== -[source, bash] +[source, text] ---- -./mvnw clean install -DskipTests -[...] - -java -jar target/demo-0.0.1-SNAPSHOT.jar +My Commands + add: Add two integers together. ---- ==== -==== -[source] ----- -shell:> ----- -==== - -A yellow `shell:>` prompt invites you to type commands. Type `add 1 2`, press `ENTER`, and admire the magic: +And you can run it: ==== -[source, bash] +[source, text] ---- shell:>add --a 1 --b 2 3 ---- ==== -You should play with the shell (hint: there is a `help` command). When you are done, type `exit` and press `ENTER`. - The rest of this document delves deeper into the whole Spring Shell programming model. diff --git a/spring-shell-docs/src/main/asciidoc/index.adoc b/spring-shell-docs/src/main/asciidoc/index.adoc index 38a45000..734239c5 100644 --- a/spring-shell-docs/src/main/asciidoc/index.adoc +++ b/spring-shell-docs/src/main/asciidoc/index.adoc @@ -6,7 +6,7 @@ Eric Bottard; Janne Valkealahti; Jay Bryant; Corneil du Plessis :experimental: // For kbd: macro :spring-shell-starter: spring-shell-starter -*{projectVersion}* +*{project-version}* (C) 2017 - 2022 VMware, Inc. diff --git a/spring-shell-docs/src/main/asciidoc/introduction.adoc b/spring-shell-docs/src/main/asciidoc/introduction.adoc index 99a19898..2e871523 100644 --- a/spring-shell-docs/src/main/asciidoc/introduction.adoc +++ b/spring-shell-docs/src/main/asciidoc/introduction.adoc @@ -12,12 +12,3 @@ the familiar Spring programming model. Spring Shell includes advanced features (such as parsing, tab completion, colorization of output, fancy ASCII-art table display, input conversion, and validation), freeing you to focus on core command logic. - -[IMPORTANT] -==== -Spring Shell 2.1.x is a major rework to bring the codebase up to date with -existing Spring Boot versions, adding new features and, especially, -making it work with GraalVM which makes command-line applications much -more relevant in a Java space. Moving to a new major version also lets -us clean up the codebase and make some needed breaking changes. -====