diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 15bf11f65..968bcc712 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -3,38 +3,13 @@ on: workflow_dispatch jobs: - # Builds documentation pdf file in cli/target/package and uploads artifact to docs - build-documentation: - name: Build documentation - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - with: - submodules: recursive - - name: Set up JDK - uses: actions/setup-java@v1 - with: - java-version: '17' - - name: Make documentation available - run: | - cd documentation - mvn -B -ntp -Dstyle.color=always -DskipTests=true package - - name: Upload documentation - uses: actions/upload-artifact@v4 - with: - name: docs - path: documentation/target/generated-docs/*.pdf - - # Downloads documentation docs artifact, builds all native images, archives native image for each os with documentation pdf file and uploads artifacts to f.e. natives-windows-latest + # Builds all native images and uploads each binary as a separate artifact build-natives: name: Build native images - needs: build-documentation runs-on: ${{ matrix.os }} strategy: matrix: os: [ windows-latest, ubuntu-latest, macos-latest, macos-13 ] - steps: - uses: actions/checkout@v3 with: @@ -45,26 +20,21 @@ jobs: distribution: 'graalvm' github-token: ${{ secrets.GITHUB_TOKEN }} native-image-job-reports: 'true' - - name: Download documentation - uses: actions/download-artifact@v4 - with: - name: docs - path: ./cli/target/package - name: Build native image shell: bash run: | cd cli - mvn -B -ntp -Dideasy.assembly.id=${{ matrix.os }} -Pnative -DskipTests=true package + mvn -B -ntp -Pnative -DskipTests=true compile - name: Upload native image uses: actions/upload-artifact@v4 with: name: natives-${{ matrix.os }} - path: cli/target/*.tar.gz + path: cli/target/ideasy* - # Downloads all native image artifacts to cli/target and builds the project for deployment to OSSRH Nexus + # Downloads all native image artifacts to cli/target and builds the project using assemblies for final deployment to OSSRH Nexus deploy: name: Build Project and Deploy - needs: [ build-documentation, build-natives ] + needs: [ build-natives ] runs-on: ubuntu-latest steps: - name: Checkout code @@ -80,11 +50,8 @@ jobs: with: pattern: natives-* path: ./cli/target/ - merge-multiple: true - - run: | - mvn -B -ntp -Dstyle.color=always install - name: Deploy to OSSRH nexus env: SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} - run: mvn --settings .mvn/settings.xml -DskipTests=true -Darchetype.test.skip=true -Dmaven.install.skip=true -Dgpg.skip=true -Dstyle.color=always -B -ntp -Pdeploy deploy + run: mvn --settings .mvn/settings.xml -DskipTests=true -Darchetype.test.skip=true -Dgpg.skip=true -Dstyle.color=always -B -ntp -Passembly,deploy deploy diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0d2d0b597..3b5e1582b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,38 +6,13 @@ on: jobs: - # Builds documentation pdf file in cli/target/package and uploads artifact to docs - build-documentation: - name: Build documentation - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - with: - submodules: recursive - - name: Set up JDK - uses: actions/setup-java@v1 - with: - java-version: '17' - - name: Make documentation available - run: | - cd documentation - mvn -B -ntp -Dstyle.color=always -DskipTests=true package - - name: Upload documentation - uses: actions/upload-artifact@v4 - with: - name: docs - path: documentation/target/generated-docs/*.pdf - - # Downloads documentation docs artifact, builds all native images, archives native image for each os with documentation pdf file and uploads artifacts to f.e. natives-windows-latest + # Adjusts the revision to the latest version, builds images for each OS type/architecture using matrix:os and uploads each binary as a separate artifact build-natives: name: Build native images - needs: build-documentation runs-on: ${{ matrix.os }} strategy: matrix: os: [ windows-latest, ubuntu-latest, macos-latest, macos-13 ] - steps: - uses: actions/checkout@v3 with: @@ -48,11 +23,6 @@ jobs: distribution: 'graalvm' github-token: ${{ secrets.GITHUB_TOKEN }} native-image-job-reports: 'true' - - name: Download documentation - uses: actions/download-artifact@v4 - with: - name: docs - path: ./cli/target/package - name: Build native image shell: bash run: | @@ -61,17 +31,20 @@ jobs: current_version="${current_version/ */}" next_version="${current_version/-SNAPSHOT/}" cd cli - mvn -B -ntp -Drevision=${next_version} -Dideasy.assembly.id=${{ matrix.os }} -Pnative -DskipTests=true package + mvn -B -ntp -Drevision=${next_version} -Pnative -DskipTests=true compile - name: Upload native image uses: actions/upload-artifact@v4 with: name: natives-${{ matrix.os }} - path: cli/target/*.tar.gz + path: cli/target/ideasy* + # Downloads all native image artifacts to cli/target and builds the project using assemblies for final deployment to Maven Central. + # The version number for the next build will be incremented automatically. + # A GitHub release and download URLs pointing to the respective OS/architecture archives on Maven Central will be created. release: name: Release on Sonatype OSS runs-on: ubuntu-latest - needs: [ build-documentation, build-natives ] + needs: build-natives steps: - name: Checkout uses: actions/checkout@v3 @@ -91,8 +64,7 @@ jobs: with: pattern: natives-* path: ./cli/target/ - merge-multiple: true - - name: Publish to Apache Maven Central + - name: Create assemblies and publish to Apache Maven Central run: | maven_config="$(cat .mvn/maven.config)" current_version="${maven_config/#*-Drevision=}" @@ -106,7 +78,7 @@ jobs: git tag -a "release/${next_version}" -m "tagged version ${next_version}" export GPG_TTY=$TTY mkdir -p ./cli/target/ - mvn --settings .mvn/settings.xml -B -ntp deploy -Pdeploy -Dgpg.pin.entry.mode=loopback -Dgpg.passphrase=${{ secrets.GPG_PASSPHRASE }} + mvn --settings .mvn/settings.xml -B -ntp deploy -Passembly,deploy -Dgpg.pin.entry.mode=loopback -Dgpg.passphrase=${{ secrets.GPG_PASSPHRASE }} env: SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 92616229d..5a987494f 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -9,6 +9,8 @@ Release with new features and bugfixes: * https://github.com/devonfw/IDEasy/issues/589[#589]: Fix NLS Bundles for Linux and MacOS * https://github.com/devonfw/IDEasy/issues/637[#637]: Added option to disable updates * https://github.com/devonfw/IDEasy/issues/764[#764]: IDEasy not working properly in CMD +* https://github.com/devonfw/IDEasy/issues/81[#81]: Implement Toolcommandlet for Kubernetes +* https://github.com/devonfw/IDEasy/issues/758[#758]: Create status commandlet * https://github.com/devonfw/IDEasy/issues/754[#754]: Again messages break processable command output The full list of changes for this release can be found in https://github.com/devonfw/IDEasy/milestone/16?closed=1[milestone 2024.12.001]. diff --git a/cli/pom.xml b/cli/pom.xml index 3b9bd19d7..34b3c44cf 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -174,6 +174,33 @@ + + assembly + + + + + org.apache.maven.plugins + maven-assembly-plugin + ${assembly.maven.plugin.version} + + + create-distribution + install + + single + + + ${project.artifactId}-${revision} + true + src/main/assembly + + + + + + + native @@ -189,7 +216,7 @@ compile-no-fork - package + compile test-native @@ -213,75 +240,6 @@ - - - org.apache.maven.plugins - maven-assembly-plugin - ${assembly.maven.plugin.version} - - - create-distribution - package - - single - - - ${project.artifactId}-${revision} - true - - /src/main/assembly/release-${ideasy.assembly.id}.xml - - - - - - - - - - - - deploy - - - - org.codehaus.mojo - build-helper-maven-plugin - 3.4.0 - - - attach-artifacts - package - - attach-artifact - - - - - ${project.build.directory}/${project.artifactId}-${revision}-windows-x64.tar.gz - tar.gz - windows-x64 - - - ${project.build.directory}/${project.artifactId}-${revision}-linux-x64.tar.gz - tar.gz - linux-x64 - - - ${project.build.directory}/${project.artifactId}-${revision}-mac-x64.tar.gz - tar.gz - mac-x64 - - - ${project.build.directory}/${project.artifactId}-${revision}-mac-arm.tar.gz - tar.gz - mac-arm - - - - - - diff --git a/cli/src/main/assembly/release-ubuntu-latest.xml b/cli/src/main/assembly/release-linux-x64.xml similarity index 76% rename from cli/src/main/assembly/release-ubuntu-latest.xml rename to cli/src/main/assembly/release-linux-x64.xml index ec3ffa35e..24c701423 100644 --- a/cli/src/main/assembly/release-ubuntu-latest.xml +++ b/cli/src/main/assembly/release-linux-x64.xml @@ -6,9 +6,15 @@ tar.gz false + + + ${settings.localRepository}/com/devonfw/tools/IDEasy/ide-doc/${project.version}/ide-doc-${project.version}.pdf + IDEasy.pdf + + - ${project.build.directory} + ${project.build.directory}/natives-ubuntu-latest ./bin ${imageName} diff --git a/cli/src/main/assembly/release-macos-latest.xml b/cli/src/main/assembly/release-mac-arm.xml similarity index 76% rename from cli/src/main/assembly/release-macos-latest.xml rename to cli/src/main/assembly/release-mac-arm.xml index 79142aa1c..d7ab6b6d7 100644 --- a/cli/src/main/assembly/release-macos-latest.xml +++ b/cli/src/main/assembly/release-mac-arm.xml @@ -6,9 +6,15 @@ tar.gz false + + + ${settings.localRepository}/com/devonfw/tools/IDEasy/ide-doc/${project.version}/ide-doc-${project.version}.pdf + IDEasy.pdf + + - ${project.build.directory} + ${project.build.directory}/natives-macos-latest ./bin ${imageName} diff --git a/cli/src/main/assembly/release-macos-13.xml b/cli/src/main/assembly/release-mac-x64.xml similarity index 77% rename from cli/src/main/assembly/release-macos-13.xml rename to cli/src/main/assembly/release-mac-x64.xml index 87102e0bd..1d5a2f109 100644 --- a/cli/src/main/assembly/release-macos-13.xml +++ b/cli/src/main/assembly/release-mac-x64.xml @@ -6,9 +6,15 @@ tar.gz false + + + ${settings.localRepository}/com/devonfw/tools/IDEasy/ide-doc/${project.version}/ide-doc-${project.version}.pdf + IDEasy.pdf + + - ${project.build.directory} + ${project.build.directory}/natives-macos-13 ./bin ${imageName} diff --git a/cli/src/main/assembly/release-windows-latest.xml b/cli/src/main/assembly/release-win-x64.xml similarity index 76% rename from cli/src/main/assembly/release-windows-latest.xml rename to cli/src/main/assembly/release-win-x64.xml index f0f2e3385..a9af3246a 100644 --- a/cli/src/main/assembly/release-windows-latest.xml +++ b/cli/src/main/assembly/release-win-x64.xml @@ -6,9 +6,15 @@ tar.gz false + + + ${settings.localRepository}/com/devonfw/tools/IDEasy/ide-doc/${project.version}/ide-doc-${project.version}.pdf + IDEasy.pdf + + - ${project.build.directory} + ${project.build.directory}/natives-windows-latest ./bin ${imageName}.exe diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java index 2c4601735..a1ab0b4d9 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java @@ -26,6 +26,7 @@ import com.devonfw.tools.ide.tool.jmc.Jmc; import com.devonfw.tools.ide.tool.kotlinc.Kotlinc; import com.devonfw.tools.ide.tool.kotlinc.KotlincNative; +import com.devonfw.tools.ide.tool.kubectl.KubeCtl; import com.devonfw.tools.ide.tool.lazydocker.LazyDocker; import com.devonfw.tools.ide.tool.mvn.Mvn; import com.devonfw.tools.ide.tool.node.Node; @@ -41,7 +42,7 @@ /** * Implementation of {@link CommandletManager}. */ -public final class CommandletManagerImpl implements CommandletManager { +public class CommandletManagerImpl implements CommandletManager { private final Map, Commandlet> commandletTypeMap; @@ -75,6 +76,7 @@ public CommandletManagerImpl(IdeContext context) { add(new EditionSetCommandlet(context)); add(new EditionListCommandlet(context)); add(new VersionCommandlet(context)); + add(new StatusCommandlet(context)); add(new RepositoryCommandlet(context)); add(new UninstallCommandlet(context)); add(new UpdateCommandlet(context)); @@ -96,6 +98,7 @@ public CommandletManagerImpl(IdeContext context) { add(new Quarkus(context)); add(new Kotlinc(context)); add(new KotlincNative(context)); + add(new KubeCtl(context)); add(new Tomcat(context)); add(new Vscode(context)); add(new Azure(context)); @@ -112,7 +115,10 @@ public CommandletManagerImpl(IdeContext context) { add(new LazyDocker(context)); } - private void add(Commandlet commandlet) { + /** + * @param commandlet the {@link Commandlet} to add. + */ + protected void add(Commandlet commandlet) { boolean hasRequiredProperty = false; List> properties = commandlet.getProperties(); diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/StatusCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/StatusCommandlet.java new file mode 100644 index 000000000..0f2e5b59c --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/StatusCommandlet.java @@ -0,0 +1,30 @@ +package com.devonfw.tools.ide.commandlet; + +import com.devonfw.tools.ide.context.IdeContext; + +/** + * {@link Commandlet} to print a status report about IDEasy. + */ +public class StatusCommandlet extends Commandlet { + + /** + * The constructor. + * + * @param context the {@link IdeContext}. + */ + public StatusCommandlet(IdeContext context) { + + super(context); + addKeyword(getName()); + } + + @Override + public String getName() { + + return "status"; + } + + @Override + public void run() { + } +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java index 0b969d2ce..06bf22e66 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java @@ -68,7 +68,7 @@ public abstract class AbstractIdeContext implements IdeContext { private Path confPath; - private Path settingsPath; + protected Path settingsPath; private Path softwarePath; @@ -76,7 +76,7 @@ public abstract class AbstractIdeContext implements IdeContext { private Path softwareRepositoryPath; - private Path pluginsPath; + protected Path pluginsPath; private Path workspacePath; @@ -108,7 +108,7 @@ public abstract class AbstractIdeContext implements IdeContext { private final FileAccess fileAccess; - private final CommandletManager commandletManager; + protected CommandletManager commandletManager; protected ToolRepository defaultToolRepository; diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/DelegatingToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/DelegatingToolCommandlet.java new file mode 100644 index 000000000..ffe188212 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/DelegatingToolCommandlet.java @@ -0,0 +1,75 @@ +package com.devonfw.tools.ide.tool; + +import java.util.Set; + +import com.devonfw.tools.ide.common.Tag; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.environment.EnvironmentVariablesFiles; +import com.devonfw.tools.ide.process.EnvironmentContext; +import com.devonfw.tools.ide.version.VersionIdentifier; + +/** + * {@link ToolCommandlet} that delegates to another ToolCommandlet. + */ +public abstract class DelegatingToolCommandlet extends ToolCommandlet { + + private Class delegateClass; + + /** + * The constructor. + * + * @param context the {@link IdeContext}. + * @param tool the {@link #getName() tool name}. + * @param tags the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of} method. + * @param delegateClass the {@link ToolCommandlet}. + */ + public DelegatingToolCommandlet(IdeContext context, String tool, Set tags, Class delegateClass) { + + super(context, tool, tags); + this.delegateClass = delegateClass; + } + + private D getDelegate() { + return getCommandlet(this.delegateClass); + } + + @Override + public final boolean install(boolean silent, EnvironmentContext environmentContext) { + return getDelegate().install(silent, environmentContext); + } + + @Override + public VersionIdentifier getInstalledVersion() { + return getDelegate().getInstalledVersion(); + } + + @Override + public String getInstalledEdition() { + return getDelegate().getInstalledEdition(); + } + + @Override + public void uninstall() { + getDelegate().uninstall(); + } + + @Override + public void listEditions() { + getDelegate().listEditions(); + } + + @Override + public void listVersions() { + getDelegate().listVersions(); + } + + @Override + public void setVersion(VersionIdentifier version, boolean hint, EnvironmentVariablesFiles destination) { + getDelegate().setVersion(version, hint, destination); + } + + @Override + public void setEdition(String edition, boolean hint, EnvironmentVariablesFiles destination) { + getDelegate().setEdition(edition, hint, destination); + } +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java index e5a7be510..53f04d5c0 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java @@ -67,7 +67,7 @@ protected void installDependencies() { } @Override - public final boolean install(boolean silent, EnvironmentContext environmentContext) { + public boolean install(boolean silent, EnvironmentContext environmentContext) { installDependencies(); VersionIdentifier configuredVersion = getConfiguredVersion(); diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java index 4bba7352f..c1f265337 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/ide/IdeToolCommandlet.java @@ -35,11 +35,11 @@ public IdeToolCommandlet(IdeContext context, String tool, Set tags) { private boolean hasIde(Set tags) { for (Tag tag : tags) { - if (tag.isAncestorOf(Tag.IDE)) { + if (tag.isAncestorOf(Tag.IDE) || (tag == Tag.IDE)) { return true; } } - throw new IllegalStateException("Tags of IdeTool hat to be connected with tag IDE: " + tags); + throw new IllegalStateException("Tags of IdeTool has to be connected with tag IDE: " + tags); } @Override @@ -62,7 +62,7 @@ protected void configureWorkspace() { Path settingsWorkspaceFolder = this.context.getSettingsPath().resolve(this.tool) .resolve(IdeContext.FOLDER_WORKSPACE); - Path genericWorkspaceFolder = this.context.getSettingsPath().resolve(IdeContext.FOLDER_WORKSPACE); + Path genericWorkspaceFolder = this.context.getSettingsPath().resolve(IdeContext.FOLDER_WORKSPACE); Path workspaceUpdateFolder = genericWorkspaceFolder.resolve(IdeContext.FOLDER_UPDATE); Path workspaceSetupFolder = genericWorkspaceFolder.resolve(IdeContext.FOLDER_SETUP); FileAccess fileAccess = this.context.getFileAccess(); diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/kubectl/KubeCtl.java b/cli/src/main/java/com/devonfw/tools/ide/tool/kubectl/KubeCtl.java new file mode 100644 index 000000000..c976413ee --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/kubectl/KubeCtl.java @@ -0,0 +1,25 @@ +package com.devonfw.tools.ide.tool.kubectl; + +import java.util.Set; + +import com.devonfw.tools.ide.common.Tag; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.tool.DelegatingToolCommandlet; +import com.devonfw.tools.ide.tool.docker.Docker; + +/** + * {@link DelegatingToolCommandlet} for Kubectl. + */ +public class KubeCtl extends DelegatingToolCommandlet { + + + /** + * The constructor. + * + * @param context the {@link IdeContext}. + */ + public KubeCtl(IdeContext context) { + + super(context, "kubectl", Set.of(Tag.KUBERNETES), Docker.class); + } +} diff --git a/cli/src/main/package/bin/ide b/cli/src/main/package/bin/ide index 3b7f1bdd1..58ee8a3d0 100644 --- a/cli/src/main/package/bin/ide +++ b/cli/src/main/package/bin/ide @@ -15,6 +15,7 @@ ide_env="$("${_IDEASY}" env --bash)" if [ $? = 0 ]; then eval "${ide_env}" if [ $# = 0 ]; then + ide status echo "IDE environment variables have been set for ${IDE_HOME} in workspace ${WORKSPACE}" fi fi diff --git a/cli/src/main/resources/nls/Help.properties b/cli/src/main/resources/nls/Help.properties index c7cfa44cc..a9c0f5c5e 100644 --- a/cli/src/main/resources/nls/Help.properties +++ b/cli/src/main/resources/nls/Help.properties @@ -62,6 +62,8 @@ cmd.kotlinc=Tool commandlet for Kotlin (compiler for JRE language). cmd.kotlinc.detail=Kotlin Compiler (kotlinc) is the command-line tool for compiling Kotlin code. Detailed documentation can be found at https://kotlinlang.org/docs/home.html cmd.kotlincnative=Tool commandlet for Kotlin-Native (compiler for JRE language). cmd.kotlincnative.detail=Kotlin/Native Compiler (kotlincnative) compiles Kotlin code to native executables. Detailed documentation can be found at https://kotlinlang.org/docs/reference/native-overview.html +cmd.kubectl=Tool commandlet for kubernetes. Detailed documentation can be found at https://kubernetes.io/docs/home/ +cmd.kubectl.detail=The kubectl commandlet allows to install and use kubernetes. On Windows WSL 2 (Windows Subsystem for Linux) has to be installed properly as a prerequisite. cmd.lazydocker=Tool commandlet for LazyDocker. cmd.lazydocker.detail=Lazydocker is a simple terminal UI for both docker and docker-compose. Detailed documentation can be found at https://github.com/jesseduffield/lazydocker cmd.list-editions=List the available editions of the selected tool. @@ -95,6 +97,8 @@ cmd.shell.detail=The interactive shell offers console users a new user experienc cmd.sonar=Tool commandlet for SonarQube. cmd.sonar.detail=SonarQube is a platform for continuous inspection of code quality. Detailed documentation can be found at https://docs.sonarqube.org/ cmd.sonar.val.command=Action to perform (START|STOP|ANALYZE) +cmd.status=Prints the status report about your IDEasy. +cmd.status.detail=To check your IDE-status (e.g. duplicated or legacy variables) as well as potential information about updates to settings you should apply\nwith ide update, run the following command: 'ide status'. cmd.terraform=Tool commandlet for Terraform. cmd.terraform.detail=Terraform is an infrastructure as code tool for managing cloud resources. Detailed documentation can be found at https://www.terraform.io/docs/index.html cmd.tomcat=Tool commandlet for Tomcat diff --git a/cli/src/main/resources/nls/Help_de.properties b/cli/src/main/resources/nls/Help_de.properties index b6210648a..f43abe941 100644 --- a/cli/src/main/resources/nls/Help_de.properties +++ b/cli/src/main/resources/nls/Help_de.properties @@ -62,6 +62,8 @@ cmd.kotlinc=Werkzeug Kommando für Kotlin (Compiler für JRE Sprache). cmd.kotlinc.detail=Der Kotlin-Compiler (kotlinc) ist das Befehlszeilentool zum Kompilieren von Kotlin-Code. Detaillierte Dokumentation ist zu finden unter https://kotlinlang.org/docs/home.html cmd.kotlincnative=Werkzeug Kommando für Kotlin-Native (Compiler für JRE Sprache). cmd.kotlincnative.detail=Der Kotlin/Native-Compiler (kotlincnative) kompiliert Kotlin-Code in native ausführbare Dateien. Detaillierte Dokumentation ist zu finden unter https://kotlinlang.org/docs/reference/native-overview.html +cmd.kubectl=Werkzeug Kommando für Kubernetes. Detaillierte Dokumentation ist zu finden unter https://kubernetes.io/docs/home/ +cmd.kubectl.detail=Der Befehl kubectl ermöglicht die Installation und Nutzung von Kubernetes. Unter Windows muss WSL 2 (Windows Subsystem for Linux) ordnungsgemäß installiert sein. cmd.lazydocker=Werkzeug Kommando für LazyDocker. cmd.lazydocker.detail=Lazydocker ist ein einfaches Kommandozeilen-Benutzer-Interface für Docker und Docker-compose. Detaillierte Dokumentation ist zu finden unter https://github.com/jesseduffield/lazydocker cmd.list-editions=Listet die verfügbaren Editionen des selektierten Werkzeugs auf. @@ -95,6 +97,8 @@ cmd.shell.detail=Die integrierte Shell bietet Konsolenbenutzer*Innen eine neue B cmd.sonar=Werkzeug Kommando für SonarQube. cmd.sonar.detail=SonarQube ist eine Plattform für die kontinuierliche Code-Qualitätsprüfung. Detaillierte Dokumentation ist zu finden unter https://docs.sonarqube.org/ cmd.sonar.val.command=Auszuführende Aktion (START|STOP|ANALYZE) +cmd.status=Gibt einen Statusbericht über IDEasy aus +cmd.status.detail=Um den Status Ihrer IDE zu überprüfen (z.B. doppelte oder veraltete Variablen) sowie potenzielle Informationen über Updates zu den Einstellungen,\ndie Sie mit dem Befehl 'ide update' anwenden sollten, führen Sie den folgenden Befehl aus: 'ide status'. cmd.terraform=Werkzeug Kommando für Terraform. cmd.terraform.detail=Terraform ist ein Tool für Infrastructure as Code zur Verwaltung von Cloud-Ressourcen. Detaillierte Dokumentation ist zu finden unter https://www.terraform.io/docs/index.html cmd.tomcat=Werkzeug Kommando für Tomcat diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/TestCommandletManager.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/TestCommandletManager.java new file mode 100644 index 000000000..33c457c74 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/TestCommandletManager.java @@ -0,0 +1,25 @@ +package com.devonfw.tools.ide.commandlet; + +import com.devonfw.tools.ide.context.IdeContext; + +/** + * Extends {@link CommandletManagerImpl} to make {@link #add(Commandlet)} method visible for testing and mocking. + */ +public class TestCommandletManager extends CommandletManagerImpl { + + /** + * @param context the {@link IdeContext}. + */ + public TestCommandletManager(IdeContext context) { + + super(context); + } + + @Override + public void add(Commandlet commandlet) { + + super.add(commandlet); + } + + +} diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java index d9d730c04..540940f8f 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java +++ b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java @@ -5,6 +5,9 @@ import java.util.HashMap; import java.util.Map; +import com.devonfw.tools.ide.commandlet.Commandlet; +import com.devonfw.tools.ide.commandlet.CommandletManager; +import com.devonfw.tools.ide.commandlet.TestCommandletManager; import com.devonfw.tools.ide.common.SystemPath; import com.devonfw.tools.ide.environment.AbstractEnvironmentVariables; import com.devonfw.tools.ide.environment.EnvironmentVariables; @@ -37,14 +40,15 @@ public class AbstractIdeTestContext extends AbstractIdeContext { private ProcessContext mockContext; + private TestCommandletManager testCommandletManager; + /** * The constructor. * * @param logger the {@link IdeLogger}. * @param workingDirectory the optional {@link Path} to current working directory. - * @param answers the automatic answers simulating a user in test. */ - public AbstractIdeTestContext(IdeStartContextImpl logger, Path workingDirectory, String... answers) { + public AbstractIdeTestContext(IdeStartContextImpl logger, Path workingDirectory) { super(logger, workingDirectory); this.answers = new String[0]; @@ -83,6 +87,10 @@ protected String readLine() { return this.answers[this.answerIndex++]; } + /** + * @param answers the answers for interactive questions in order (e.g. if "yes" is given as first answer, this will be used to answer the first + * question). + */ public void setAnswers(String... answers) { requireMutable(); this.answers = answers; @@ -103,9 +111,7 @@ public IdeProgressBar prepareProgressBar(String taskName, long size) { IdeProgressBarTestImpl progressBar = new IdeProgressBarTestImpl(taskName, size); IdeProgressBarTestImpl duplicate = this.progressBarMap.put(taskName, progressBar); // If we have multiple downloads or unpacking, we may have an existing "Downloading" or "Unpacking" key - if ((!taskName.equals("Downloading")) && (!taskName.equals("Unpacking"))) { - assert duplicate == null; - } + assert (taskName.equals("Downloading")) || (taskName.equals("Unpacking")) || duplicate == null; return progressBar; } @@ -199,4 +205,44 @@ public void setDefaultToolRepository(ToolRepository defaultToolRepository) { this.defaultToolRepository = defaultToolRepository; } + + /** + * @param settingsPath the new value of {@link #getSettingsPath()}. + */ + public void setSettingsPath(Path settingsPath) { + + this.settingsPath = settingsPath; + } + + /** + * @param pluginsPath the new value of {@link #getPluginsPath()}. + */ + public void setPluginsPath(Path pluginsPath) { + + this.pluginsPath = pluginsPath; + } + + /** + * @param commandletManager the new value of {@link #getCommandletManager()}. + */ + public void setCommandletManager(CommandletManager commandletManager) { + if (commandletManager instanceof TestCommandletManager tcm) { + this.testCommandletManager = tcm; + } else { + this.testCommandletManager = null; + } + this.commandletManager = commandletManager; + } + + /** + * @param commandlet the {@link Commandlet} to add to {@link #getCommandletManager()} for testing. + */ + public void addCommandlet(Commandlet commandlet) { + + if (this.testCommandletManager == null) { + setCommandletManager(new TestCommandletManager(this)); + } + this.testCommandletManager.add(commandlet); + } + } diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/ide/IdeToolDummyCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/ide/IdeToolDummyCommandletTest.java new file mode 100644 index 000000000..300806d7d --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/ide/IdeToolDummyCommandletTest.java @@ -0,0 +1,89 @@ +package com.devonfw.tools.ide.tool.ide; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import com.devonfw.tools.ide.commandlet.Commandlet; +import com.devonfw.tools.ide.common.Tag; +import com.devonfw.tools.ide.context.AbstractIdeContextTest; +import com.devonfw.tools.ide.context.AbstractIdeTestContext; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.context.IdeSlf4jContext; +import com.devonfw.tools.ide.process.ProcessErrorHandling; +import com.devonfw.tools.ide.process.ProcessMode; +import com.devonfw.tools.ide.process.ProcessResult; +import com.devonfw.tools.ide.process.ProcessResultImpl; +import com.devonfw.tools.ide.step.Step; +import com.devonfw.tools.ide.tool.plugin.ToolPluginDescriptor; +import com.devonfw.tools.ide.version.GenericVersionRange; + +/** + * Test of {@link IdeToolCommandlet} using {@link IdeToolDummyCommandlet}. + */ +public class IdeToolDummyCommandletTest extends AbstractIdeContextTest { + + /** + * Run the dummy commandlet and test that only active plugins are passed to installPlugin method. + * + * @param tempDir the {@link TempDir}. + */ + @Test + public void testDummyCommandlet(@TempDir Path tempDir) { + + AbstractIdeTestContext context = new IdeSlf4jContext(); + context.setPluginsPath(tempDir); + context.setSettingsPath(Path.of("src/test/resources/settings/dummy")); + IdeToolDummyCommandlet dummyCommandlet = new IdeToolDummyCommandlet(context, "dummy", Set.of(Tag.IDE)); + + context.addCommandlet(dummyCommandlet); + + Commandlet dummy = context.getCommandletManager().getCommandlet("dummy"); + assertThat(dummy).isSameAs(dummyCommandlet); + dummy.run(); + assertThat(dummyCommandlet.installedPlugins).hasSize(1); + ToolPluginDescriptor plugin = dummyCommandlet.installedPlugins.get(0); + assertThat(plugin.id()).isEqualTo("plugin1-id"); + assertThat(plugin.url()).isEqualTo("https://dummy.com/plugins/plugin1-url"); + } + + /** + * Dummy commandlet extending {@link IdeToolCommandlet} for testing. + */ + public static class IdeToolDummyCommandlet extends IdeToolCommandlet { + + final List installedPlugins; + + IdeToolDummyCommandlet(IdeContext context, String tool, Set tags) { + + super(context, tool, tags); + this.installedPlugins = new ArrayList<>(); + } + + @Override + protected void configureWorkspace() { + + // disable workspace configuration since we have no IDE_HOME and therefore no settings + } + + @Override + public ProcessResult runTool(ProcessMode processMode, GenericVersionRange toolVersion, ProcessErrorHandling errorHandling, String... args) { + + // skip installation but trigger postInstall to test mocked plugin installation + postInstall(true); + return new ProcessResultImpl(0, List.of(), List.of()); + } + + @Override + public void installPlugin(ToolPluginDescriptor plugin, Step step) { + + this.installedPlugins.add(plugin); + step.success("Dummy plugin " + plugin.name() + " installed."); + } + + } +} diff --git a/cli/src/test/resources/settings/dummy/dummy/plugins/plugin1.properties b/cli/src/test/resources/settings/dummy/dummy/plugins/plugin1.properties new file mode 100644 index 000000000..138d14b61 --- /dev/null +++ b/cli/src/test/resources/settings/dummy/dummy/plugins/plugin1.properties @@ -0,0 +1,4 @@ +plugin_id=plugin1-id +plugin_active=true +plugin_url=https://dummy.com/plugins/plugin1-url +tags=dummy diff --git a/cli/src/test/resources/settings/dummy/dummy/plugins/plugin2.properties b/cli/src/test/resources/settings/dummy/dummy/plugins/plugin2.properties new file mode 100644 index 000000000..d7e1205af --- /dev/null +++ b/cli/src/test/resources/settings/dummy/dummy/plugins/plugin2.properties @@ -0,0 +1,4 @@ +plugin_id=plugin2-id +plugin_active=false +plugin_url=https://dummy.com/plugins/plugin2-url +tags=dummy2 diff --git a/documentation/github-release.adoc b/documentation/github-release.adoc new file mode 100644 index 000000000..5f61a86cb --- /dev/null +++ b/documentation/github-release.adoc @@ -0,0 +1,109 @@ +:toc: +toc::[] + += Building IDEasy Releases + +We have spent quite some effort to fully automate our release processes with github actions. +This documentation aims to explain how it works. + +== Usage + +Usage is very simple: + +* Every night a SNAPSHOT release is build and deployed for internal developers and retesting of bugfixes. +This process if fully automated. +See link:setup.adoc#testing-snapshot-releases[testing-snapshot-releases] for details. +* When all is prepared for the next release, we run the https://github.com/devonfw/IDEasy/actions/workflows/release.yml[release] workflow. +With according permissions, you will see a gray `Run workflow` button that opens an overlay with a green `Run workflow` button that you need to press (correct branch `main` is preselected). +That is all you need to create a new release. +It will automatically appear on https://github.com/devonfw/IDEasy/releases[github releases]. +Please note that the staging process of the release to maven central has some delay so the download links may only work ~1h after the release job completed. + +After an official release has been published the following manual steps need to be performed: + +* Close the release milestone that has just been released on github (see https://github.com/devonfw/IDEasy/milestones[milestones] and pay attention to the links `Edit`, `Close`, and `Delete`). +* Verify and potentially edit/update the new `SNAPSHOT` version in https://github.com/devonfw/IDEasy/blob/main/.mvn/maven.config[maven.config]. +The release workflow will always increase the last digit (micro-version) and often we instead want to increase the month segment. +Example: the version was `2025.01.001-SNAPSHOT` and therefore the release `2025.01.001` has been build and deployed. +Then the new version was set to `2025.01.002-SNAPSHOT`. +In case that the January is almost over and the next release is planned for Februrary, you instead want to change the version to `2025.02.001-SNAPSHOT`. +* Finally, you edit our https://github.com/devonfw/IDEasy/blob/main/CHANGELOG.adoc[CHANGELOG] and add the next planned version at the top. +Include the header and footer lines but leave the content in between as a blank line (where the issues will be added as bullet-list). + +== How it works + +To make all this work a lot of different technical aspects work together. +Here we consider fundamental things such as https://www.java.com[Java], https://maven.apache.org/[Maven], and https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions[Github Actions] as given. + +=== OSSRH + +* As a preparation we have requested https://central.sonatype.org/publish/publish-guide/[OSSRH] (Open Source Repository Hosting) for our project. +* We have created according account and registered PGP key +* We have added PGP key and account credentials to our github secrets (see also our https://github.com/devonfw/IDEasy/blob/main/.mvn/settings.xml[settings.xml] that is referenced from maven calls in github actions). +* In our parent POM we have configured a https://github.com/devonfw/maven-parent/blob/9d31509d5f25c96fa1ec8b4f8cd2c341349b4df2/pom.xml#L290-L342[deploy profile] with additional steps for deploying via OSSRH + +=== Native Images + +* In order to make our CLI tool work fast and without having Java installed as a pre-requisite, we use https://www.graalvm.org/latest/reference-manual/native-image/[GraalVM native image] technology to compile our Java code to a platform specific binary. +* To build such a binary, we have configured a https://github.com/devonfw/IDEasy/blob/10fc17b42ad4d465ee96fe5af7739d99a5132f51/cli/pom.xml#L204-L245[native profile]. +* This uses the https://graalvm.github.io/native-build-tools/latest/maven-plugin.html[native-maven-plugin] to actually build the native binary. +* Since GraalVM does not support cross-platform compilation, we need to run such build on a (virtual) machine operated by the according OS. +* The awesome github platform supports arbitrary operating systems and architectures to run builds on from github actions. +* Therefore we run a job (`build-natives`) using a `strategy` with `matrix.os` configured to all the operating systems and architectures we support. +The configured values are container image names that match to a specific platform but are a little cryptic. +You can find a list of these container images https://github.com/actions/runner-images?tab=readme-ov-file#available-images[here]. +* This will spawn a build for each configured OS in a container on the according OS and architecture. +All these builds will run in parallel. +* We use the `setup-graalvm` action to make GraalVM available in the build container. +* To build the actual native image, we simply invoke `compile` goal via maven but only in the `cli` module (`cd cli`). +Here we need to activate the `native` profile (`-Pnative`). +Also we can skip the tests since they are done in the main build anyway (`-DskipTests`). +Since we want to build the native image for a specific version, we compute that version (e.g. by removing the `-SNAPSHOT` suffix in case of an official release) and override it via `revision` variable (`-Drevision=${...}`). +* Only after all these spawned builds have completed successfully the main build continues with the next job (`release` or `deploy`) what we call the "Main Build". +* In order to use the native image(s) that have been build on different machines, we use the https://github.com/actions/upload-artifact[upload-artifact] action to upload them to an github exchange store and https://github.com/actions/download-artifact[download-artifact] action to get them all into the main build. + +=== Main Build + +* Like with any maven project, we use the `deploy` goal via maven to compile, test, package, install and finally deploy our project. +* Here, we build the entire project with all modules including the documentation that gets generated as PDF (see https://github.com/devonfw/docgen/[docgen] and https://github.com/devonfw/IDEasy/blob/main/documentation/pom.xml[documentation/pom.xml] for details). +* Since the documentation PDF gets attached as artifact it is installed and deployed to the maven repository. +* In the `cli` module the build we compile our code again with `javac` and run the automated tests. +* However, the real compilation to the native image has already happened before (see above section). +* Content that is rather static like script files can be found in https://github.com/devonfw/IDEasy/tree/main/cli/src/main/package[src/main/package]. +* We use a https://github.com/devonfw/IDEasy/blob/10fc17b42ad4d465ee96fe5af7739d99a5132f51/cli/pom.xml#L130-L170[specific resource configuration] to filter and copy such files to `target/package`. +* Therefore, the main step here is the `Assembly` creating an release for every target platform (called by maven with `-Passembly,deploy`). +* Finally, all artifacts are signed, installed and deployed to OSSRH nexus. +* In case of an official release they are automatically staged to maven central and also published to our https://github.com/devonfw/IDEasy/releases[github releases] using the GitHub CLI (`gh release create`). +* Also only for official releases, we also write changes to our version (see https://github.com/devonfw/IDEasy/blob/main/.mvn/maven.config[maven.config]) and commit them. +* The bumped release version also is stored as annotated tag via git. +* Also the next `SNAPSHOT` version is set and committed. +* After all was successful we push our commits and the tag - in case the build failed, nothing will be pushed and commits will be lost. + +=== Assembly + +* In order to build a `.tar.gz` archive with all the content needed in a release, we have configured a https://github.com/devonfw/IDEasy/blob/10fc17b42ad4d465ee96fe5af7739d99a5132f51/cli/pom.xml#L177-L203[assembly profile]. +* This uses the https://maven.apache.org/plugins/maven-assembly-plugin/[maven-assembly-plugin] to build such compressed archive. +* For each platform (OS and architecture), we have an according configuration file in https://github.com/devonfw/IDEasy/tree/main/cli/src/main/assembly[src/main/assembly]. +* The assembly descriptor file format is described https://maven.apache.org/plugins/maven-assembly-plugin/assembly.html[here]. +* Each such file includes the according native image. +Therefore, the proper container image name from the `matrix.os` (see above) has to be referenced (see https://github.com/devonfw/IDEasy/blob/10fc17b42ad4d465ee96fe5af7739d99a5132f51/cli/src/main/assembly/release-linux-x64.xml#L17[here] for an example). +* Additionally we reference the https://github.com/devonfw/IDEasy/blob/10fc17b42ad4d465ee96fe5af7739d99a5132f51/cli/src/main/assembly/release-linux-x64.xml#L24[package content] (see above) and configure exclusions to ensure that only content relevant for the according platform gets included (e.g. `*.bat` files are only included in Windows releases but not for Linux or Mac). +* Also the configuration includes the documentation as PDF (see https://github.com/devonfw/IDEasy/blob/10fc17b42ad4d465ee96fe5af7739d99a5132f51/cli/src/main/assembly/release-linux-x64.xml#L10-L13[here]). + +=== Workflows + +And finally we put it all togehter as github action workflow: + +* https://github.com/devonfw/IDEasy/blob/main/.github/workflows/release.yml[release.yml] is the workflow for an official release. +* https://github.com/devonfw/IDEasy/blob/main/.github/workflows/nightly-build.yml[nightly-build.yml] is the workflow for a SNAPSHOT release. +* For the nightly-build we use a trick to skip the build if no changes happened to our git in the last 24h to avoid waste: +We created another https://github.com/devonfw/IDEasy/blob/main/.github/workflows/check-for-updates.yml[check-for-updates.yml] workflow that runs every night and checks for such updates. +Only if recent changes where pushed to git on `main`, the `nightly-build` job is triggered and otherwise the build ends without any further action. + +Both `release` and `nightly-build` workflow use the `workflow_dispatch` trigger allowing them to be run manually as described above. +However, the `nightly-build` is typically only triggered from `check-for-updates` workflow automatically. +But for testing some change with GraalVM specific behaviour during the day, we sometimes also trigger the `nightly-build` workflow manually. + + + + diff --git a/documentation/tool-vendor-plea.adoc b/documentation/tool-vendor-plea.adoc index f9cc6207a..13e44806d 100644 --- a/documentation/tool-vendor-plea.adoc +++ b/documentation/tool-vendor-plea.adoc @@ -60,6 +60,18 @@ That is great! But please follow best-practices and common sense and publish them as a new release with a new unique and increased version. * Tools that have been published and then after some time the download disappeared leading to errors like 404 +=== Proper file format + +On Linux and Mac binaries and scripts need executable permission to be run (`chmod a+x «binary»`). +Since ZIP files are unable to store such metainformation you should be smart and not use ZIP as file format at least for all other platforms than Windows. +As a best practice never use ZIP but instead something like `*.tar.gz`. + +Unfortunately many famous tools violate this principle and provide ZIP files for Linux and Mac. +As a result users unzip the package and then get a warm welcome when running the tool: +``` +bash: «binary»: Permission denied +``` + === Comparable versions We really do not want to dicatate anything regarding your versioning scheme. @@ -144,7 +156,7 @@ Also a good convention is the end options argument (`--`) that e.g. allows you t Every tool should have the CLI option to print its version via `-v` or `--version`. Please note that Java used to have `-version` instead of `--version` but later added support also for the latter (thanks guys!). -Further, invoking this feature should *printing the version number and nothing else*. +Further, invoking this feature should *print the version number and nothing else*. Please consider that often you need to make a choice on a version in a shell script and tool vendors make life for this use-case unneccesary hard if they print lots of other information alongside. If you want to do this add an extra option (e.g. `--verbose --version`) for this but *never* print it when `-v` was given as only argument.