diff --git a/apps/jreleaser-tool-provider/gradle.properties b/apps/jreleaser-tool-provider/gradle.properties
new file mode 100644
index 00000000..92ff0132
--- /dev/null
+++ b/apps/jreleaser-tool-provider/gradle.properties
@@ -0,0 +1,19 @@
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Copyright 2020 Andres Almiray.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+project_description = JReleaser Tool Provider
\ No newline at end of file
diff --git a/apps/jreleaser-tool-provider/jreleaser-tool-provider.gradle b/apps/jreleaser-tool-provider/jreleaser-tool-provider.gradle
new file mode 100644
index 00000000..4a055e97
--- /dev/null
+++ b/apps/jreleaser-tool-provider/jreleaser-tool-provider.gradle
@@ -0,0 +1,33 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+dependencies {
+ api project(':jreleaser')
+}
+
+tasks.withType(JavaCompile) { JavaCompile c ->
+ c.sourceCompatibility = JavaVersion.VERSION_1_9
+ c.targetCompatibility = JavaVersion.VERSION_1_9
+}
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(9)
+ }
+}
+
diff --git a/apps/jreleaser-tool-provider/src/main/java/module-info.java b/apps/jreleaser-tool-provider/src/main/java/module-info.java
new file mode 100644
index 00000000..e87660cf
--- /dev/null
+++ b/apps/jreleaser-tool-provider/src/main/java/module-info.java
@@ -0,0 +1,25 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+module org.kordamp.jreleaser.tool {
+ exports org.kordamp.jreleaser.tool;
+ requires org.kordamp.jreleaser.app;
+
+ provides java.util.spi.ToolProvider with
+ org.kordamp.jreleaser.tool.JReleaserToolProvider;
+}
\ No newline at end of file
diff --git a/apps/jreleaser-tool-provider/src/main/java/org/kordamp/jreleaser/tool/JReleaserToolProvider.java b/apps/jreleaser-tool-provider/src/main/java/org/kordamp/jreleaser/tool/JReleaserToolProvider.java
new file mode 100644
index 00000000..c4bc35fa
--- /dev/null
+++ b/apps/jreleaser-tool-provider/src/main/java/org/kordamp/jreleaser/tool/JReleaserToolProvider.java
@@ -0,0 +1,36 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.tool;
+
+import java.io.PrintWriter;
+import java.util.spi.ToolProvider;
+import org.kordamp.jreleaser.app.Main;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class JReleaserToolProvider implements ToolProvider {
+ public String name() {
+ return "jreleaser";
+ }
+
+ public int run(PrintWriter out, PrintWriter err, String... args) {
+ return Main.run(out, err, args);
+ }
+}
diff --git a/apps/jreleaser/gradle.properties b/apps/jreleaser/gradle.properties
new file mode 100644
index 00000000..46c8a9c2
--- /dev/null
+++ b/apps/jreleaser/gradle.properties
@@ -0,0 +1,19 @@
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Copyright 2020 Andres Almiray.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+project_description = JReleaser
\ No newline at end of file
diff --git a/apps/jreleaser/jreleaser.gradle b/apps/jreleaser/jreleaser.gradle
new file mode 100644
index 00000000..821f0c29
--- /dev/null
+++ b/apps/jreleaser/jreleaser.gradle
@@ -0,0 +1,52 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+plugins {
+ id 'application'
+}
+
+config {
+ info {
+ specification { enabled = true }
+ }
+}
+
+dependencies {
+ api project(':jreleaser-tools')
+ api project(':jreleaser-templates')
+ api project(':jreleaser-config-yaml')
+ api('com.google.guava:guava:30.0-jre') { transitive = false }
+
+ annotationProcessor "info.picocli:picocli-codegen:$picocliVersion"
+ api "info.picocli:picocli:$picocliVersion"
+ api "org.slf4j:slf4j-api:$slf4jVersion"
+ runtimeOnly "org.slf4j:slf4j-simple:$slf4jVersion"
+}
+
+mainClassName = 'org.kordamp.jreleaser.app.Main'
+
+processResources {
+ inputs.property('version', project.version)
+ filesMatching(['**/*.properties']) {
+ expand(
+ 'version': project.version,
+ 'id': 'jrleaser',
+ 'name': 'jrleaser'
+ )
+ }
+}
\ No newline at end of file
diff --git a/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/AbstractCommand.java b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/AbstractCommand.java
new file mode 100644
index 00000000..82fa5aa1
--- /dev/null
+++ b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/AbstractCommand.java
@@ -0,0 +1,94 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.app;
+
+import org.kordamp.jreleaser.app.internal.Colorizer;
+import org.kordamp.jreleaser.app.internal.JReleaserLoggerAdapter;
+import org.kordamp.jreleaser.util.Logger;
+import picocli.CommandLine;
+
+import java.nio.file.Path;
+import java.util.concurrent.Callable;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CommandLine.Command
+abstract class AbstractCommand implements Callable {
+ protected Logger logger;
+
+ @CommandLine.Option(names = {"-d", "--debug"},
+ description = "Set log level to debug.")
+ boolean debug;
+
+ @CommandLine.Option(names = {"-i", "--info"},
+ description = "Set log level to info.")
+ boolean info;
+
+ @CommandLine.Option(names = {"-w", "--warn"},
+ description = "Set log level to warn.")
+ boolean warn;
+
+ @CommandLine.Option(names = {"-q", "--quiet"},
+ description = "Log errors only.")
+ boolean quiet;
+
+ @CommandLine.Option(names = {"--basedir"},
+ description = "Base directory")
+ Path basedir;
+
+ @CommandLine.Spec
+ CommandLine.Model.CommandSpec spec;
+
+ protected abstract Main parent();
+
+ public Integer call() {
+ Banner.display();
+
+ JReleaserLoggerAdapter.Level level = JReleaserLoggerAdapter.Level.WARN;
+ if (debug) {
+ level = JReleaserLoggerAdapter.Level.DEBUG;
+ System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug");
+ } else if (info) {
+ level = JReleaserLoggerAdapter.Level.INFO;
+ System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "info");
+ } else if (warn) {
+ level = JReleaserLoggerAdapter.Level.WARN;
+ System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "warn");
+ } else if (quiet) {
+ level = JReleaserLoggerAdapter.Level.ERROR;
+ System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "error");
+ }
+
+ logger = new JReleaserLoggerAdapter(parent().out, level);
+
+ try {
+ execute();
+ } catch (HaltExecutionException e) {
+ return 1;
+ } catch (Exception e) {
+ e.printStackTrace(new Colorizer(parent().out));
+ return 1;
+ }
+
+ return 0;
+ }
+
+ protected abstract void execute();
+}
diff --git a/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/AbstractModelCommand.java b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/AbstractModelCommand.java
new file mode 100644
index 00000000..27f5a414
--- /dev/null
+++ b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/AbstractModelCommand.java
@@ -0,0 +1,97 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.app;
+
+import org.kordamp.jreleaser.app.internal.JReleaserLoggerAdapter;
+import org.kordamp.jreleaser.config.JReleaserConfigLoader;
+import org.kordamp.jreleaser.model.JReleaserModel;
+import org.kordamp.jreleaser.model.JReleaserModelValidator;
+import org.kordamp.jreleaser.util.Logger;
+import picocli.CommandLine;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CommandLine.Command
+public abstract class AbstractModelCommand extends AbstractCommand {
+ @CommandLine.Option(names = {"--config-file"},
+ description = "The config file")
+ Path configFile;
+
+ @CommandLine.ParentCommand
+ Main parent;
+
+ Path actualConfigFile;
+ Path actualBasedir;
+
+ @Override
+ protected Main parent() {
+ return parent;
+ }
+
+ protected void execute() {
+ resolveConfigFile();
+ resolveBasedir();
+ consumeModel(resolveModel());
+ }
+
+ protected void resolveConfigFile() {
+ actualConfigFile = null != configFile ? configFile : Paths.get(".").normalize().resolve(".jreleaser.yml");
+ if (!Files.exists(actualConfigFile)) {
+ spec.commandLine().getErr()
+ .println(spec.commandLine().getColorScheme().errorText("Missing required option: '--config-file='"));
+ spec.commandLine().usage(parent.out);
+ throw new HaltExecutionException();
+ }
+ }
+
+ private void resolveBasedir() {
+ actualBasedir = null != basedir ? basedir : actualConfigFile.toAbsolutePath().getParent();
+ if (!Files.exists(actualBasedir)) {
+ spec.commandLine().getErr()
+ .println(spec.commandLine().getColorScheme().errorText("Missing required option: '--basedir='"));
+ spec.commandLine().usage(parent.out);
+ throw new HaltExecutionException();
+ }
+ }
+
+ protected abstract void consumeModel(JReleaserModel jreleaserModel);
+
+ private JReleaserModel resolveModel() {
+ try {
+ JReleaserModel jreleaserModel = JReleaserConfigLoader.loadConfig(actualConfigFile);
+ List errors = JReleaserModelValidator.validate(logger, actualBasedir, jreleaserModel);
+ if (!errors.isEmpty()) {
+ Logger logger = new JReleaserLoggerAdapter(parent.out);
+ logger.error("== JReleaser ==");
+ errors.forEach(logger::error);
+ throw new JReleaserException("JReleaser with " + actualConfigFile.toAbsolutePath() + " has not been properly configured.");
+ }
+
+ return jreleaserModel;
+ } catch (IllegalArgumentException e) {
+ throw new JReleaserException("Unexpected error when parsing configuration from " + actualConfigFile.toAbsolutePath(), e);
+ }
+ }
+}
diff --git a/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/AbstractProcessorCommand.java b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/AbstractProcessorCommand.java
new file mode 100644
index 00000000..f7e8e6ce
--- /dev/null
+++ b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/AbstractProcessorCommand.java
@@ -0,0 +1,96 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.app;
+
+import org.kordamp.jreleaser.model.Distribution;
+import org.kordamp.jreleaser.model.JReleaserModel;
+import org.kordamp.jreleaser.tools.DistributionProcessor;
+import org.kordamp.jreleaser.tools.ToolProcessingException;
+import picocli.CommandLine;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CommandLine.Command
+public abstract class AbstractProcessorCommand extends AbstractModelCommand {
+ @CommandLine.Option(names = {"--fail-fast"},
+ description = "Fail fast")
+ boolean failFast = true;
+
+ Path outputDirectory;
+
+ @Override
+ protected void consumeModel(JReleaserModel jreleaserModel) {
+ outputDirectory = actualBasedir.resolve("out");
+
+ Path checksumDirectory = computeChecksums(jreleaserModel, outputDirectory);
+
+ List exceptions = new ArrayList<>();
+ for (Distribution distribution : jreleaserModel.getDistributions().values()) {
+ for (String toolName : Distribution.supportedTools()) {
+ try {
+ DistributionProcessor processor = createDistributionProcessor(jreleaserModel,
+ checksumDirectory,
+ outputDirectory,
+ distribution,
+ toolName);
+
+ consumeProcessor(processor);
+ } catch (ToolProcessingException e) {
+ if (failFast) throw new IllegalStateException("Unexpected error", e);
+ exceptions.add(e);
+ logger.warn("Unexpected error", e);
+ }
+ }
+ }
+
+ if (!exceptions.isEmpty()) {
+ throw new IllegalStateException("There were " + exceptions.size() + " failure(s)");
+ }
+ }
+
+ protected abstract void consumeProcessor(DistributionProcessor processor) throws ToolProcessingException;
+
+ private DistributionProcessor createDistributionProcessor(JReleaserModel jreleaserModel,
+ Path checksumDirectory,
+ Path outputDirectory,
+ Distribution distribution,
+ String toolName) {
+ return DistributionProcessor.builder()
+ .logger(logger)
+ .model(jreleaserModel)
+ .distributionName(distribution.getName())
+ .toolName(toolName)
+ .checksumDirectory(checksumDirectory)
+ .outputDirectory(outputDirectory
+ .resolve("jreleaser")
+ .resolve(distribution.getName())
+ .resolve(toolName))
+ .build();
+ }
+
+ protected Path computeChecksums(JReleaserModel jreleaserModel, Path outputDirectory) {
+ return outputDirectory.resolve("jreleaser")
+ .resolve("checksums");
+ }
+}
diff --git a/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Banner.java b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Banner.java
new file mode 100644
index 00000000..bdb3949f
--- /dev/null
+++ b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Banner.java
@@ -0,0 +1,102 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.app;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.text.MessageFormat;
+import java.util.ResourceBundle;
+import java.util.Scanner;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+final class Banner {
+ private static final Banner b = new Banner();
+ private final ResourceBundle bundle = ResourceBundle.getBundle(Banner.class.getName());
+ private final String productVersion = bundle.getString("product.version");
+ private final String productId = bundle.getString("product.id");
+ private final String productName = bundle.getString("product.name");
+ private final String banner = MessageFormat.format(bundle.getString("product.banner"), productName, productVersion);
+
+ private Banner() {
+ // nooop
+ }
+
+ public static void display() {
+ try {
+ File parent = new File(System.getProperty("user.home"), "/.jreleaser/caches");
+ File markerFile = getMarkerFile(parent, b);
+ if (!markerFile.exists()) {
+ System.out.println(b.banner);
+ markerFile.getParentFile().mkdirs();
+ PrintStream out = new PrintStream(new FileOutputStream(markerFile));
+ out.println("1");
+ out.close();
+ writeQuietly(markerFile, "1");
+ } else {
+ try {
+ int count = Integer.parseInt(readQuietly(markerFile));
+ if (count < 3) {
+ System.out.println(b.banner);
+ }
+ writeQuietly(markerFile, (count + 1) + "");
+ } catch (NumberFormatException e) {
+ writeQuietly(markerFile, "1");
+ System.out.println(b.banner);
+ }
+ }
+ } catch (IOException ignored) {
+ // noop
+ }
+ }
+
+ private static void writeQuietly(File file, String text) {
+ try {
+ PrintStream out = new PrintStream(new FileOutputStream(file));
+ out.println(text);
+ out.close();
+ } catch (IOException ignored) {
+ // ignored
+ }
+ }
+
+ private static String readQuietly(File file) {
+ try {
+ Scanner in = new Scanner(new FileInputStream(file));
+ return in.next();
+ } catch (Exception ignored) {
+ return "";
+ }
+ }
+
+ private static File getMarkerFile(File parent, Banner b) {
+ return new File(parent,
+ "kordamp" +
+ File.separator +
+ b.productId +
+ File.separator +
+ b.productVersion +
+ File.separator +
+ "marker.txt");
+ }
+}
diff --git a/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Config.java b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Config.java
new file mode 100644
index 00000000..d2038bb1
--- /dev/null
+++ b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Config.java
@@ -0,0 +1,35 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.app;
+
+import org.kordamp.jreleaser.app.internal.JReleaserModelPrinter;
+import org.kordamp.jreleaser.model.JReleaserModel;
+import picocli.CommandLine;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CommandLine.Command(name = "config",
+ description = "Displays current configuration")
+public class Config extends AbstractModelCommand {
+ @Override
+ protected void consumeModel(JReleaserModel jreleaserModel) {
+ new JReleaserModelPrinter(parent.out).print(jreleaserModel.asMap());
+ }
+}
diff --git a/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/HaltExecutionException.java b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/HaltExecutionException.java
new file mode 100644
index 00000000..2d4dc5c4
--- /dev/null
+++ b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/HaltExecutionException.java
@@ -0,0 +1,28 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.app;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class HaltExecutionException extends RuntimeException {
+ public HaltExecutionException() {
+ super();
+ }
+}
diff --git a/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Init.java b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Init.java
new file mode 100644
index 00000000..967ea238
--- /dev/null
+++ b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Init.java
@@ -0,0 +1,82 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.app;
+
+import org.kordamp.jreleaser.templates.TemplateUtils;
+import picocli.CommandLine;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Scanner;
+
+import static java.nio.file.StandardOpenOption.CREATE;
+import static java.nio.file.StandardOpenOption.CREATE_NEW;
+import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
+import static java.nio.file.StandardOpenOption.WRITE;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CommandLine.Command(name = "init",
+ description = "Creates a jrelaser config file")
+public class Init extends AbstractCommand {
+ @CommandLine.Option(names = {"--overwrite"},
+ description = "Overwrite existing files")
+ boolean overwrite;
+
+ @CommandLine.ParentCommand
+ Main parent;
+
+ @Override
+ protected Main parent() {
+ return parent;
+ }
+
+ protected void execute() {
+ try {
+ Path outputDirectory = null != basedir ? basedir : Paths.get(".").normalize();
+ Path outputFile = outputDirectory.resolve(".jreleaser.yml");
+
+ Reader template = TemplateUtils.resolveTemplate(logger, Init.class,
+ "META-INF/jreleaser/templates/jreleaser.yml.tpl");
+
+ logger.info("Writing file " + outputFile.toAbsolutePath());
+ try (Writer writer = Files.newBufferedWriter(outputFile, (overwrite ? CREATE : CREATE_NEW), WRITE, TRUNCATE_EXISTING);
+ Scanner scanner = new Scanner(template)) {
+ while (scanner.hasNextLine()) {
+ writer.write(scanner.nextLine() + System.lineSeparator());
+ }
+ } catch (FileAlreadyExistsException e) {
+ logger.error("File {} already exists and overwrite was set to false.", outputFile.toAbsolutePath());
+ return;
+ }
+
+ if (!quiet) {
+ parent.out.println("JReleaser initialized at " + outputDirectory.toAbsolutePath());
+ }
+ } catch (IllegalStateException | IOException e) {
+ throw new JReleaserException("Unexpected error", e);
+ }
+ }
+}
diff --git a/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/JReleaserException.java b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/JReleaserException.java
new file mode 100644
index 00000000..cea1d91b
--- /dev/null
+++ b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/JReleaserException.java
@@ -0,0 +1,36 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.app;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class JReleaserException extends RuntimeException {
+ public JReleaserException(String message) {
+ super(message);
+ }
+
+ public JReleaserException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public JReleaserException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Main.java b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Main.java
new file mode 100644
index 00000000..bfea6efe
--- /dev/null
+++ b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Main.java
@@ -0,0 +1,64 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.app;
+
+import picocli.CommandLine;
+
+import java.io.PrintWriter;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CommandLine.Command(name = "jreleaser",
+ description = "jreleaser",
+ mixinStandardHelpOptions = true,
+ versionProvider = Versions.class,
+ subcommands = {Init.class, Config.class, Template.class, Prepare.class, Package.class})
+public class Main implements Runnable {
+ PrintWriter out;
+ PrintWriter err;
+
+ @CommandLine.Spec
+ CommandLine.Model.CommandSpec spec;
+
+ public void run() {
+ Banner.display();
+
+ spec.commandLine().usage(out);
+ }
+
+ public static void main(String[] args) {
+ System.exit(run(args));
+ }
+
+ public static int run(String... args) {
+ Main cmd = new Main();
+ CommandLine commandLine = new CommandLine(cmd);
+ cmd.out = commandLine.getOut();
+ cmd.err = commandLine.getErr();
+ return commandLine.execute(args);
+ }
+
+ public static int run(PrintWriter out, PrintWriter err, String... args) {
+ Main cmd = new Main();
+ cmd.out = out;
+ cmd.err = err;
+ return new CommandLine(cmd).execute(args);
+ }
+}
diff --git a/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Package.java b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Package.java
new file mode 100644
index 00000000..1539ff2b
--- /dev/null
+++ b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Package.java
@@ -0,0 +1,38 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.app;
+
+import org.kordamp.jreleaser.tools.DistributionProcessor;
+import org.kordamp.jreleaser.tools.ToolProcessingException;
+import picocli.CommandLine;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CommandLine.Command(name = "package",
+ description = "Packages all distributions")
+public class Package extends AbstractProcessorCommand {
+ @Override
+ protected void consumeProcessor(DistributionProcessor processor) throws ToolProcessingException {
+ if (processor.packageDistribution() && !quiet) {
+ parent.out.println("Packaged " + processor.getDistributionName() +
+ " distribution with tool " + processor.getToolName());
+ }
+ }
+}
diff --git a/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Prepare.java b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Prepare.java
new file mode 100644
index 00000000..1adfca02
--- /dev/null
+++ b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Prepare.java
@@ -0,0 +1,77 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.app;
+
+import com.google.common.hash.HashCode;
+import com.google.common.hash.Hashing;
+import com.google.common.io.Files;
+import org.kordamp.jreleaser.model.Artifact;
+import org.kordamp.jreleaser.model.Distribution;
+import org.kordamp.jreleaser.model.JReleaserModel;
+import org.kordamp.jreleaser.tools.DistributionProcessor;
+import org.kordamp.jreleaser.tools.ToolProcessingException;
+import picocli.CommandLine;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CommandLine.Command(name = "prepare",
+ description = "Prepares all distributions")
+public class Prepare extends AbstractProcessorCommand {
+ @Override
+ protected void consumeProcessor(DistributionProcessor processor) throws ToolProcessingException {
+ if (processor.prepareDistribution()) {
+ parent.out.println("Prepared " + processor.getDistributionName() +
+ " distribution with tool " + processor.getToolName());
+ }
+ }
+
+ @Override
+ protected Path computeChecksums(JReleaserModel jreleaserModel, Path outputDirectory) {
+ Path checksumDirectory = super.computeChecksums(jreleaserModel, outputDirectory);
+
+ try {
+ java.nio.file.Files.createDirectories(checksumDirectory);
+ } catch (IOException e) {
+ throw new JReleaserException("Unexpected error creating checksum directory.", e);
+ }
+
+ jreleaserModel.getDistributions().values()
+ .stream().flatMap((Function>) distribution -> distribution.getArtifacts()
+ .stream()).forEach(artifact -> {
+ Path inputFile = Paths.get(artifact.getPath()).toAbsolutePath();
+ String fileName = inputFile.getFileName().toString();
+ Path checksumFilePath = checksumDirectory.resolve(fileName + ".sha256");
+ try {
+ HashCode hashCode = Files.asByteSource(inputFile.toFile()).hash(Hashing.sha256());
+ Files.write(hashCode.toString().getBytes(), checksumFilePath.toFile());
+ } catch (IOException e) {
+ throw new JReleaserException("Unexpected error creating checksum for " + inputFile, e);
+ }
+ });
+
+ return checksumDirectory;
+ }
+}
diff --git a/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Template.java b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Template.java
new file mode 100644
index 00000000..f3d3d00e
--- /dev/null
+++ b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Template.java
@@ -0,0 +1,94 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.app;
+
+import org.kordamp.jreleaser.model.Distribution;
+import org.kordamp.jreleaser.templates.TemplateGenerationException;
+import org.kordamp.jreleaser.templates.TemplateGenerator;
+import picocli.CommandLine;
+
+import java.nio.file.Path;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CommandLine.Command(name = "template",
+ description = "Generates a tool template")
+public class Template extends AbstractCommand {
+ @CommandLine.Option(names = {"--distribution-name"},
+ description = "The name of the distribution",
+ required = true)
+ String distributionName;
+
+ @CommandLine.Option(names = {"--tool-name"},
+ description = "The name of the tool",
+ required = true)
+ String toolName;
+
+ @CommandLine.Option(names = {"--distribution-type"},
+ description = "The type of the distribution",
+ required = true,
+ defaultValue = "BINARY")
+ Distribution.DistributionType distributionType;
+
+ @CommandLine.Option(names = {"--overwrite"},
+ description = "Overwrite existing files")
+ boolean overwrite;
+
+ @CommandLine.ParentCommand
+ Main parent;
+
+ @Override
+ protected Main parent() {
+ return parent;
+ }
+
+ protected void execute() {
+ try {
+ if (null == basedir) {
+ spec.commandLine().getErr()
+ .println(spec.commandLine().getColorScheme().errorText("Missing required option: '--basedir='"));
+ spec.commandLine().usage(parent.out);
+ throw new HaltExecutionException();
+ }
+
+ Path outputDirectory = basedir
+ .resolve("src")
+ .resolve("distributions");
+
+ boolean result = TemplateGenerator.builder()
+ .logger(logger)
+ .distributionName(distributionName)
+ .distributionType(distributionType)
+ .toolName(toolName)
+ .outputDirectory(outputDirectory)
+ .overwrite(overwrite)
+ .build()
+ .generate();
+
+ if (result && !quiet) {
+ parent.out.println("Template generated at " +
+ outputDirectory.resolve(distributionName).resolve(toolName)
+ .normalize().toAbsolutePath());
+ }
+ } catch (TemplateGenerationException e) {
+ throw new JReleaserException("Unexpected error", e);
+ }
+ }
+}
diff --git a/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Versions.java b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Versions.java
new file mode 100644
index 00000000..7adde4ca
--- /dev/null
+++ b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/Versions.java
@@ -0,0 +1,91 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.app;
+
+import picocli.CommandLine;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.ResourceBundle;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+import static org.kordamp.jreleaser.util.StringUtils.isNotBlank;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class Versions implements CommandLine.IVersionProvider {
+ private static final ResourceBundle bundle = ResourceBundle.getBundle(Versions.class.getName());
+ private static final String JRELEASER_VERSION = bundle.getString("jreleaser_version");
+
+ @Override
+ public String[] getVersion() throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ banner(new PrintStream(baos));
+ return baos.toString().split("\n");
+ }
+
+ public static void banner(PrintStream out) {
+ Manifest manifest = findMyManifest();
+ if (null != manifest) {
+ String version = manifest.getMainAttributes().getValue(Attributes.Name.SPECIFICATION_VERSION);
+ String buildDate = manifest.getMainAttributes().getValue("Build-Date");
+ String buildTime = manifest.getMainAttributes().getValue("Build-Time");
+ String buildRevision = manifest.getMainAttributes().getValue("Build-Revision");
+ boolean additionalInfo = isNotBlank(buildDate) || isNotBlank(buildTime) || isNotBlank(buildRevision);
+
+ out.println("------------------------------------------------------------");
+ out.println("jreleaser " + version);
+ out.println("------------------------------------------------------------");
+ if (additionalInfo) {
+ if (isNotBlank(buildDate) && isNotBlank(buildTime)) {
+ out.println("Build time: " + buildDate + " " + buildTime);
+ }
+ if (isNotBlank(buildRevision)) out.println("Revision: " + buildRevision);
+ out.println("------------------------------------------------------------");
+ }
+ } else {
+ out.println("jreleaser " + JRELEASER_VERSION);
+ }
+ }
+
+ private static Manifest findMyManifest() {
+ try {
+ Enumeration urls = Versions.class.getClassLoader().getResources("META-INF/MANIFEST.MF");
+ while (urls.hasMoreElements()) {
+ URL url = urls.nextElement();
+ Manifest manifest = new Manifest(url.openStream());
+ if (manifest.getMainAttributes().containsKey(Attributes.Name.SPECIFICATION_TITLE)) {
+ String specificationTitle = manifest.getMainAttributes().getValue(Attributes.Name.SPECIFICATION_TITLE);
+ if ("jreleaser".equals(specificationTitle)) {
+ return manifest;
+ }
+ }
+ }
+ } catch (IOException e) {
+ // well, this sucks
+ }
+
+ return null;
+ }
+}
diff --git a/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/internal/Colorizer.java b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/internal/Colorizer.java
new file mode 100644
index 00000000..d8f5ba6f
--- /dev/null
+++ b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/internal/Colorizer.java
@@ -0,0 +1,37 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.app.internal;
+
+import picocli.CommandLine;
+
+import java.io.PrintWriter;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class Colorizer extends PrintWriter {
+ public Colorizer(PrintWriter delegate) {
+ super(delegate, true);
+ }
+
+ @Override
+ public void print(String s) {
+ super.print(CommandLine.Help.Ansi.AUTO.text("@|red " + s + "|@"));
+ }
+}
diff --git a/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/internal/JReleaserLoggerAdapter.java b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/internal/JReleaserLoggerAdapter.java
new file mode 100644
index 00000000..a56d5fef
--- /dev/null
+++ b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/internal/JReleaserLoggerAdapter.java
@@ -0,0 +1,162 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.app.internal;
+
+import org.kordamp.jreleaser.util.Logger;
+import org.slf4j.helpers.MessageFormatter;
+import picocli.CommandLine;
+
+import java.io.PrintWriter;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class JReleaserLoggerAdapter implements Logger {
+ private final PrintWriter out;
+ private final Level level;
+
+ public JReleaserLoggerAdapter(PrintWriter out) {
+ this(out, Level.WARN);
+ }
+
+ public JReleaserLoggerAdapter(PrintWriter out, Level level) {
+ this.out = out;
+ this.level = level;
+ }
+
+ @Override
+ public void debug(String message) {
+ if (isLevelEnabled(Level.DEBUG)) {
+ out.println(Level.DEBUG + message);
+ }
+ }
+
+ @Override
+ public void info(String message) {
+ if (isLevelEnabled(Level.INFO)) {
+ out.println(Level.INFO + message);
+ }
+ }
+
+ @Override
+ public void warn(String message) {
+ if (isLevelEnabled(Level.WARN)) {
+ out.println(Level.WARN + message);
+ }
+ }
+
+ @Override
+ public void error(String message) {
+ if (isLevelEnabled(Level.ERROR)) {
+ out.println(Level.ERROR + message);
+ }
+ }
+
+ @Override
+ public void debug(String message, Object... args) {
+ if (isLevelEnabled(Level.DEBUG)) {
+ out.println(Level.DEBUG + MessageFormatter.arrayFormat(message, args).getMessage());
+ }
+ }
+
+ @Override
+ public void info(String message, Object... args) {
+ if (isLevelEnabled(Level.INFO)) {
+ out.println(Level.INFO + MessageFormatter.arrayFormat(message, args).getMessage());
+ }
+ }
+
+ @Override
+ public void warn(String message, Object... args) {
+ if (isLevelEnabled(Level.WARN)) {
+ out.println(Level.WARN + MessageFormatter.arrayFormat(message, args).getMessage());
+ }
+ }
+
+ @Override
+ public void error(String message, Object... args) {
+ if (isLevelEnabled(Level.ERROR)) {
+ out.println(Level.ERROR + MessageFormatter.arrayFormat(message, args).getMessage());
+ }
+ }
+
+ @Override
+ public void debug(String message, Throwable throwable) {
+ if (isLevelEnabled(Level.DEBUG)) {
+ out.println(Level.DEBUG + message);
+ printThrowable(throwable);
+ }
+ }
+
+ @Override
+ public void info(String message, Throwable throwable) {
+ if (isLevelEnabled(Level.INFO)) {
+ out.println(Level.INFO + message);
+ printThrowable(throwable);
+ }
+ }
+
+ @Override
+ public void warn(String message, Throwable throwable) {
+ if (isLevelEnabled(Level.WARN)) {
+ out.println(Level.WARN + message);
+ printThrowable(throwable);
+ }
+ }
+
+ @Override
+ public void error(String message, Throwable throwable) {
+ if (isLevelEnabled(Level.ERROR)) {
+ out.println(Level.ERROR + message);
+ printThrowable(throwable);
+ }
+ }
+
+ private void printThrowable(Throwable throwable) {
+ if (null != throwable) {
+ throwable.printStackTrace(new Colorizer(out));
+ }
+ }
+
+ private boolean isLevelEnabled(Level requested) {
+ return requested.ordinal() >= level.ordinal();
+ }
+
+ public enum Level {
+ DEBUG("cyan"),
+ INFO("blue"),
+ WARN("yellow"),
+ ERROR("red");
+
+ private final String color;
+
+ Level(String color) {
+ this.color = color;
+ }
+
+ @Override
+ public String toString() {
+ return "[" + colorize(name()) + "] ";
+ }
+
+ private String colorize(String input) {
+ return CommandLine.Help.Ansi.AUTO.string("@|" + color + " " + input + "|@");
+ }
+ }
+}
diff --git a/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/internal/JReleaserModelPrinter.java b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/internal/JReleaserModelPrinter.java
new file mode 100644
index 00000000..beed8087
--- /dev/null
+++ b/apps/jreleaser/src/main/java/org/kordamp/jreleaser/app/internal/JReleaserModelPrinter.java
@@ -0,0 +1,41 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.app.internal;
+
+import picocli.CommandLine;
+
+import java.io.PrintWriter;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class JReleaserModelPrinter extends org.kordamp.jreleaser.model.JReleaserModelPrinter {
+ public JReleaserModelPrinter(PrintWriter out) {
+ super(out);
+ }
+
+ public JReleaserModelPrinter(PrintWriter out, boolean showSecrets) {
+ super(out, showSecrets);
+ }
+
+ @Override
+ protected String color(String color, String input) {
+ return CommandLine.Help.Ansi.AUTO.string("@|" + color + " " + input + "|@");
+ }
+}
diff --git a/apps/jreleaser/src/main/module/module-info.java b/apps/jreleaser/src/main/module/module-info.java
new file mode 100644
index 00000000..62b16f5a
--- /dev/null
+++ b/apps/jreleaser/src/main/module/module-info.java
@@ -0,0 +1,31 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+module org.kordamp.jreleaser.app {
+ exports org.kordamp.jreleaser.app;
+ requires org.kordamp.jreleaser.util;
+ requires org.kordamp.jreleaser.model;
+ requires org.kordamp.jreleaser.config;
+ requires org.kordamp.jreleaser.tools;
+ requires org.kordamp.jreleaser.templates;
+ requires info.picocli;
+ requires org.slf4j;
+ requires org.slf4j.simple;
+
+ opens org.kordamp.jreleaser.app to info.picocli;
+}
\ No newline at end of file
diff --git a/apps/jreleaser/src/main/resources/META-INF/jreleaser/templates/jreleaser.yml.tpl b/apps/jreleaser/src/main/resources/META-INF/jreleaser/templates/jreleaser.yml.tpl
new file mode 100644
index 00000000..d78b80e4
--- /dev/null
+++ b/apps/jreleaser/src/main/resources/META-INF/jreleaser/templates/jreleaser.yml.tpl
@@ -0,0 +1,19 @@
+project:
+ name: awesomeApp
+ version: 0.0.0-SNAPSHOT
+ description: Awesome App
+ longDescription: Awesome App
+ website: https://acme.com/awesomeapp
+ authors:
+ - Joe Cool
+ license: Apache-2
+ javaVersion: 8
+
+release:
+ repoType: GITHUB
+ repoOwner: acme
+
+distributions:
+ awesomeApp:
+ artifacts:
+ - path: /path/to/app/awesomeApp-0.0.0-SNAPSHOT.zip
\ No newline at end of file
diff --git a/apps/jreleaser/src/main/resources/org/kordamp/jreleaser/app/Banner.properties b/apps/jreleaser/src/main/resources/org/kordamp/jreleaser/app/Banner.properties
new file mode 100644
index 00000000..d841c0dc
--- /dev/null
+++ b/apps/jreleaser/src/main/resources/org/kordamp/jreleaser/app/Banner.properties
@@ -0,0 +1,22 @@
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Copyright 2020 Andres Almiray.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+product.version=$version
+product.id=$id
+product.name=$name
+product.banner={0} {1}. Consider becoming a patron at https://www.patreon.com/aalmiray
\ No newline at end of file
diff --git a/apps/jreleaser/src/main/resources/org/kordamp/jreleaser/app/Versions.properties b/apps/jreleaser/src/main/resources/org/kordamp/jreleaser/app/Versions.properties
new file mode 100644
index 00000000..3c10a483
--- /dev/null
+++ b/apps/jreleaser/src/main/resources/org/kordamp/jreleaser/app/Versions.properties
@@ -0,0 +1,19 @@
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Copyright 2020 Andres Almiray.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+jreleaser_version = ${version}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 00000000..33c49996
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,105 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+config {
+ info {
+ description = 'jreleaser'
+ inceptionYear = '2020'
+ tags = ['release']
+
+ specification {
+ enabled = true
+ }
+
+ implementation {
+ enabled = true
+ }
+ }
+
+ docs {
+ javadoc {
+ autoLinks {
+ enabled = false
+ }
+ }
+ }
+
+ bintray {
+ enabled = false
+ }
+}
+
+allprojects {
+ repositories {
+ gradlePluginPortal()
+ jcenter()
+ mavenLocal()
+ }
+
+ tasks.withType(GenerateModuleMetadata) {
+ enabled = false
+ }
+}
+
+subprojects {
+ if (project.name != 'guide') {
+ config {
+ info {
+ description = project.project_description
+ }
+
+ bintray {
+ enabled = true
+ name = project.name
+ }
+ }
+
+ tasks.withType(JavaCompile) { JavaCompile c ->
+ c.sourceCompatibility = JavaVersion.VERSION_1_8
+ c.targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ tasks.withType(GroovyCompile) { GroovyCompile c ->
+ c.sourceCompatibility = JavaVersion.VERSION_1_8
+ c.targetCompatibility = JavaVersion.VERSION_1_8
+ }
+
+ if (project.projectDir.parentFile.name != 'plugins' &&
+ project.name != 'jreleaser-tool-provider') {
+ jar {
+ multiRelease = true
+ moduleInfoPath = 'src/main/module/module-info.java'
+ }
+ }
+
+ license {
+ exclude('build/**')
+ exclude('**/*.tpl')
+ }
+
+ dependencies {
+ testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
+ testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
+
+ testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
+ }
+
+ test {
+ useJUnitPlatform()
+ }
+ }
+}
diff --git a/core/jreleaser-config-yaml/gradle.properties b/core/jreleaser-config-yaml/gradle.properties
new file mode 100644
index 00000000..aa48a345
--- /dev/null
+++ b/core/jreleaser-config-yaml/gradle.properties
@@ -0,0 +1,19 @@
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Copyright 2020 Andres Almiray.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+project_description = JReleaser YAML config support
\ No newline at end of file
diff --git a/core/jreleaser-config-yaml/jreleaser-config-yaml.gradle b/core/jreleaser-config-yaml/jreleaser-config-yaml.gradle
new file mode 100644
index 00000000..111108db
--- /dev/null
+++ b/core/jreleaser-config-yaml/jreleaser-config-yaml.gradle
@@ -0,0 +1,25 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+dependencies {
+ api project(':jreleaser-config')
+ api "org.yaml:snakeyaml:$snakeYamlVersion"
+
+ compileOnly "org.kordamp.jipsy:jipsy:${jipsyVersion}"
+ annotationProcessor "org.kordamp.jipsy:jipsy:${jipsyVersion}"
+}
\ No newline at end of file
diff --git a/core/jreleaser-config-yaml/src/main/java/org/kordamp/jreleaser/config/yaml/YamlJReleaserConfigParser.java b/core/jreleaser-config-yaml/src/main/java/org/kordamp/jreleaser/config/yaml/YamlJReleaserConfigParser.java
new file mode 100644
index 00000000..b00092a3
--- /dev/null
+++ b/core/jreleaser-config-yaml/src/main/java/org/kordamp/jreleaser/config/yaml/YamlJReleaserConfigParser.java
@@ -0,0 +1,71 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.config.yaml;
+
+import org.kordamp.jipsy.ServiceProviderFor;
+import org.kordamp.jreleaser.config.JReleaserConfigParser;
+import org.kordamp.jreleaser.model.Artifact;
+import org.kordamp.jreleaser.model.JReleaserModel;
+import org.kordamp.jreleaser.model.Plug;
+import org.kordamp.jreleaser.model.Slot;
+import org.yaml.snakeyaml.TypeDescription;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.constructor.Constructor;
+import org.yaml.snakeyaml.introspector.Property;
+import org.yaml.snakeyaml.introspector.PropertyUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@ServiceProviderFor(JReleaserConfigParser.class)
+public class YamlJReleaserConfigParser implements JReleaserConfigParser {
+ @Override
+ public boolean supports(Path configFile) {
+ String fileName = configFile.getFileName().toString();
+ return fileName.endsWith(".yml") || fileName.endsWith(".yaml");
+ }
+
+ @Override
+ public JReleaserModel parse(InputStream inputStream) throws IOException {
+ Constructor c = new Constructor(JReleaserModel.class);
+ TypeDescription td = new TypeDescription(JReleaserModel.class);
+ td.addPropertyParameters("artifacts", Artifact.class);
+ td.addPropertyParameters("plugs", Plug.class);
+ td.addPropertyParameters("slots", Slot.class);
+ c.addTypeDescription(td);
+
+ c.setPropertyUtils(new PropertyUtils() {
+ @Override
+ public Property getProperty(Class extends Object> type, String name) {
+ if (name.equals("class")) {
+ name = "clazz";
+ }
+ return super.getProperty(type, name);
+ }
+ });
+
+ Yaml yaml = new Yaml(c);
+
+ return yaml.load(inputStream);
+ }
+}
diff --git a/core/jreleaser-config-yaml/src/main/module/module-info.java b/core/jreleaser-config-yaml/src/main/module/module-info.java
new file mode 100644
index 00000000..0c52e0c6
--- /dev/null
+++ b/core/jreleaser-config-yaml/src/main/module/module-info.java
@@ -0,0 +1,29 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+module org.kordamp.jreleaser.config.yaml {
+ exports org.kordamp.jreleaser.config.yaml;
+ requires org.kordamp.jreleaser.config;
+ requires org.kordamp.jreleaser.model;
+ requires org.yaml.snakeyaml;
+ requires static org.kordamp.jipsy;
+ requires static org.kordamp.jipsy.annotations;
+
+ provides org.kordamp.jreleaser.config.JReleaserConfigParser
+ with org.kordamp.jreleaser.config.yaml.YamlJReleaserConfigParser;
+}
\ No newline at end of file
diff --git a/core/jreleaser-config/gradle.properties b/core/jreleaser-config/gradle.properties
new file mode 100644
index 00000000..df3d7f5b
--- /dev/null
+++ b/core/jreleaser-config/gradle.properties
@@ -0,0 +1,19 @@
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Copyright 2020 Andres Almiray.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+project_description = JReleaser config support
\ No newline at end of file
diff --git a/core/jreleaser-config/jreleaser-config.gradle b/core/jreleaser-config/jreleaser-config.gradle
new file mode 100644
index 00000000..cc845253
--- /dev/null
+++ b/core/jreleaser-config/jreleaser-config.gradle
@@ -0,0 +1,21 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+dependencies {
+ api project(':jreleaser-model')
+}
\ No newline at end of file
diff --git a/core/jreleaser-config/src/main/java/org/kordamp/jreleaser/config/JReleaserConfigLoader.java b/core/jreleaser-config/src/main/java/org/kordamp/jreleaser/config/JReleaserConfigLoader.java
new file mode 100644
index 00000000..6cca5f3c
--- /dev/null
+++ b/core/jreleaser-config/src/main/java/org/kordamp/jreleaser/config/JReleaserConfigLoader.java
@@ -0,0 +1,46 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.config;
+
+import org.kordamp.jreleaser.model.JReleaserModel;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.ServiceLoader;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class JReleaserConfigLoader {
+ public static JReleaserModel loadConfig(Path configFile) {
+ ServiceLoader parsers = ServiceLoader.load(JReleaserConfigParser.class, JReleaserConfigParser.class.getClassLoader());
+
+ for (JReleaserConfigParser parser : parsers) {
+ if (parser.supports(configFile)) {
+ try (InputStream inputStream = configFile.toUri().toURL().openStream()) {
+ return parser.parse(inputStream);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Unexpected error parsing config file. " + configFile, e);
+ }
+ }
+ }
+ throw new IllegalArgumentException("Unsupported config format. " + configFile);
+ }
+}
diff --git a/core/jreleaser-config/src/main/java/org/kordamp/jreleaser/config/JReleaserConfigParser.java b/core/jreleaser-config/src/main/java/org/kordamp/jreleaser/config/JReleaserConfigParser.java
new file mode 100644
index 00000000..26494ec7
--- /dev/null
+++ b/core/jreleaser-config/src/main/java/org/kordamp/jreleaser/config/JReleaserConfigParser.java
@@ -0,0 +1,50 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.config;
+
+import org.kordamp.jreleaser.model.JReleaserModel;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+
+/**
+ * Allows external configuration to be parsed with a custom format.
+ *
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public interface JReleaserConfigParser {
+ /**
+ * Whether the given config file format is supported or not.
+ * Implementors would typically look at the file extension.
+ *
+ * @param configFile the configuration file to inspect
+ * @return {@code true} if the given format is supported, {@code false} otherwise.
+ */
+ boolean supports(Path configFile);
+
+ /**
+ * Reads and parses external configuration into a {@code JReleaserModel} instance.
+ *
+ * @param inputStream the configuration's input source
+ * @return a configured {@code JReleaserModel} instance, should never return {@code null}.
+ * @throws IOException if an error occurs while reading from the {@code InputStream}.
+ */
+ JReleaserModel parse(InputStream inputStream) throws IOException;
+}
diff --git a/core/jreleaser-config/src/main/module/module-info.java b/core/jreleaser-config/src/main/module/module-info.java
new file mode 100644
index 00000000..835965ef
--- /dev/null
+++ b/core/jreleaser-config/src/main/module/module-info.java
@@ -0,0 +1,24 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+module org.kordamp.jreleaser.config {
+ exports org.kordamp.jreleaser.config;
+ requires org.kordamp.jreleaser.model;
+
+ uses org.kordamp.jreleaser.config.JReleaserConfigParser;
+}
\ No newline at end of file
diff --git a/core/jreleaser-model/gradle.properties b/core/jreleaser-model/gradle.properties
new file mode 100644
index 00000000..d6674ea8
--- /dev/null
+++ b/core/jreleaser-model/gradle.properties
@@ -0,0 +1,19 @@
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Copyright 2020 Andres Almiray.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+project_description = JReleaser Model
\ No newline at end of file
diff --git a/core/jreleaser-model/jreleaser-model.gradle b/core/jreleaser-model/jreleaser-model.gradle
new file mode 100644
index 00000000..5b512d17
--- /dev/null
+++ b/core/jreleaser-model/jreleaser-model.gradle
@@ -0,0 +1,21 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+dependencies {
+ api project(':jreleaser-utils')
+}
\ No newline at end of file
diff --git a/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/AbstractDomain.java b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/AbstractDomain.java
new file mode 100644
index 00000000..0c951015
--- /dev/null
+++ b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/AbstractDomain.java
@@ -0,0 +1,29 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.model;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+abstract class AbstractDomain implements Serializable {
+ public abstract Map asMap();
+}
diff --git a/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/AbstractTool.java b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/AbstractTool.java
new file mode 100644
index 00000000..15a3be7c
--- /dev/null
+++ b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/AbstractTool.java
@@ -0,0 +1,110 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.model;
+
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+abstract class AbstractTool extends AbstractDomain implements Tool {
+ protected final String toolName;
+ protected final Map extraProperties = new LinkedHashMap<>();
+ protected Boolean enabled;
+ protected boolean enabledSet;
+ protected Path templateDirectory;
+
+ protected AbstractTool(String toolName) {
+ this.toolName = toolName;
+ }
+
+ void setAll(AbstractTool tool) {
+ this.enabled = tool.enabled;
+ this.enabledSet = tool.enabledSet;
+ this.templateDirectory = tool.templateDirectory;
+ setExtraProperties(tool.extraProperties);
+ }
+
+ @Override
+ public Boolean isEnabled() {
+ return enabled != null && enabled;
+ }
+
+ @Override
+ public void setEnabled(Boolean enabled) {
+ this.enabledSet = true;
+ this.enabled = enabled;
+ }
+
+ @Override
+ public boolean isEnabledSet() {
+ return enabledSet;
+ }
+
+ @Override
+ public String getToolName() {
+ return toolName;
+ }
+
+ @Override
+ public Path getTemplateDirectory() {
+ return templateDirectory;
+ }
+
+ @Override
+ public void setTemplateDirectory(Path templateDirectory) {
+ this.templateDirectory = templateDirectory;
+ }
+
+ @Override
+ public Map getExtraProperties() {
+ return extraProperties;
+ }
+
+ @Override
+ public void setExtraProperties(Map extraProperties) {
+ this.extraProperties.clear();
+ this.extraProperties.putAll(extraProperties);
+ }
+
+ @Override
+ public void addExtraProperties(Map extraProperties) {
+ this.extraProperties.putAll(extraProperties);
+ }
+
+ @Override
+ public final Map asMap() {
+ if (!isEnabled()) return Collections.emptyMap();
+
+ Map props = new LinkedHashMap<>();
+ props.put("enabled", isEnabled());
+ props.put("templateDirectory", templateDirectory);
+ asMap(props);
+ props.put("extraProperties", extraProperties);
+
+ Map map = new LinkedHashMap<>();
+ map.put(getToolName(), props);
+ return map;
+ }
+
+ protected abstract void asMap(Map props);
+}
diff --git a/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Artifact.java b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Artifact.java
new file mode 100644
index 00000000..f5a94eb5
--- /dev/null
+++ b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Artifact.java
@@ -0,0 +1,73 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.model;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class Artifact extends AbstractDomain {
+ private String path;
+ private String hash;
+ private String osClassifier;
+ private String javaVersion;
+
+ public String getPath() {
+ return path;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ public String getHash() {
+ return hash;
+ }
+
+ public void setHash(String hash) {
+ this.hash = hash;
+ }
+
+ public String getOsClassifier() {
+ return osClassifier;
+ }
+
+ public void setOsClassifier(String osClassifier) {
+ this.osClassifier = osClassifier;
+ }
+
+ public String getJavaVersion() {
+ return javaVersion;
+ }
+
+ public void setJavaVersion(String javaVersion) {
+ this.javaVersion = javaVersion;
+ }
+
+ public Map asMap() {
+ Map map = new LinkedHashMap<>();
+ map.put("path", path);
+ map.put("hash", hash);
+ map.put("osClassifier", osClassifier);
+ map.put("javaVersion", javaVersion);
+ return map;
+ }
+}
diff --git a/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Brew.java b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Brew.java
new file mode 100644
index 00000000..ecee0437
--- /dev/null
+++ b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Brew.java
@@ -0,0 +1,66 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.model;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class Brew extends AbstractTool {
+ public static final String TOOL_NAME = "brew";
+
+ private final Map dependencies = new LinkedHashMap<>();
+
+ public Brew() {
+ super(TOOL_NAME);
+ }
+
+ void setAll(Brew brew) {
+ super.setAll(brew);
+ setDependencies(brew.dependencies);
+ }
+
+ public Map getDependencies() {
+ return dependencies;
+ }
+
+ public void setDependencies(Map dependencies) {
+ this.dependencies.clear();
+ this.dependencies.putAll(dependencies);
+ }
+
+ public void addDependencies(Map dependencies) {
+ this.dependencies.putAll(dependencies);
+ }
+
+ public void addDependency(String key, String value) {
+ dependencies.put(key, value);
+ }
+
+ public void addDependency(String key) {
+ dependencies.put(key, "");
+ }
+
+ @Override
+ protected void asMap(Map props) {
+ props.put("dependencies", dependencies);
+ }
+}
diff --git a/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Chocolatey.java b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Chocolatey.java
new file mode 100644
index 00000000..f68c9c48
--- /dev/null
+++ b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Chocolatey.java
@@ -0,0 +1,37 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.model;
+
+import java.util.Map;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class Chocolatey extends AbstractTool {
+ public static final String TOOL_NAME = "chocolatey";
+
+ public Chocolatey() {
+ super(TOOL_NAME);
+ }
+
+ @Override
+ protected void asMap(Map props) {
+ // empty
+ }
+}
diff --git a/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Distribution.java b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Distribution.java
new file mode 100644
index 00000000..f78759d8
--- /dev/null
+++ b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Distribution.java
@@ -0,0 +1,218 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.kordamp.jreleaser.util.StringUtils.isBlank;
+import static org.kordamp.jreleaser.util.StringUtils.isNotBlank;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class Distribution extends Packagers implements ExtraProperties {
+ private final List tags = new ArrayList<>();
+ private final Map extraProperties = new LinkedHashMap<>();
+ private final List artifacts = new ArrayList<>();
+ private String name;
+ private DistributionType type = DistributionType.BINARY;
+ private String executable;
+ private String javaVersion;
+
+ void setAll(Distribution distribution) {
+ super.setAll(distribution);
+ this.name = distribution.name;
+ this.type = distribution.type;
+ this.executable = distribution.executable;
+ this.javaVersion = distribution.javaVersion;
+ setTags(distribution.tags);
+ setExtraProperties(distribution.extraProperties);
+ setArtifacts(distribution.artifacts);
+ }
+
+ public DistributionType getType() {
+ return type;
+ }
+
+ public void setType(DistributionType type) {
+ this.type = type;
+ }
+
+ public void setType(String type) {
+ this.type = DistributionType.valueOf(type.replaceAll(" ", "_")
+ .replaceAll("-", "_")
+ .toUpperCase());
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getExecutable() {
+ return executable;
+ }
+
+ public void setExecutable(String executable) {
+ this.executable = executable;
+ }
+
+ public String getJavaVersion() {
+ return javaVersion;
+ }
+
+ public void setJavaVersion(String javaVersion) {
+ this.javaVersion = javaVersion;
+ }
+
+ public List getArtifacts() {
+ return artifacts;
+ }
+
+ public void setArtifacts(List artifacts) {
+ this.artifacts.clear();
+ this.artifacts.addAll(artifacts);
+ }
+
+ public void addArtifacts(List artifacts) {
+ this.artifacts.addAll(artifacts);
+ }
+
+ public void addArtifact(Artifact artifact) {
+ if (null != artifact) {
+ this.artifacts.add(artifact);
+ }
+ }
+
+ public List getTags() {
+ return tags;
+ }
+
+ public void setTags(List tags) {
+ this.tags.clear();
+ this.tags.addAll(tags);
+ }
+
+ public void addTags(List tags) {
+ this.tags.addAll(tags);
+ }
+
+ public void addTag(String tag) {
+ if (isNotBlank(tag)) {
+ this.tags.add(tag.trim());
+ }
+ }
+
+ public void removeTag(String tag) {
+ if (isNotBlank(tag)) {
+ this.tags.remove(tag.trim());
+ }
+ }
+
+ @Override
+ public Map getExtraProperties() {
+ return extraProperties;
+ }
+
+ @Override
+ public void setExtraProperties(Map extraProperties) {
+ this.extraProperties.putAll(extraProperties);
+ }
+
+ @Override
+ public void addExtraProperties(Map extraProperties) {
+ this.extraProperties.putAll(extraProperties);
+ }
+
+ // --== TOOLs ==--
+
+ public T findTool(String name) {
+ if (isBlank(name)) {
+ throw new IllegalArgumentException("Tool name must not be blank");
+ }
+
+ return resolveTool(name);
+ }
+
+ public T getTool(String name) {
+ T tool = findTool(name);
+ if (null != tool) {
+ return tool;
+ }
+ throw new IllegalArgumentException("Tool '" + name + "' has not been configured");
+ }
+
+ private T resolveTool(String name) {
+ switch (name.toLowerCase().trim()) {
+ case Brew.TOOL_NAME:
+ return (T) getBrew();
+ case Chocolatey.TOOL_NAME:
+ return (T) getChocolatey();
+ case Scoop.TOOL_NAME:
+ return (T) getScoop();
+ case Snap.TOOL_NAME:
+ return (T) getSnap();
+ default:
+ throw new IllegalArgumentException("Unsupported tool '" + name + "'");
+ }
+ }
+
+ @Override
+ public Map asMap() {
+ Map props = new LinkedHashMap<>();
+ props.put("type", type);
+ props.put("executable", executable);
+ props.put("javaVersion", javaVersion);
+ props.put("artifacts", artifacts.stream()
+ .map(Artifact::asMap)
+ .collect(Collectors.toList()));
+ props.put("tags", tags);
+ props.put("extraProperties", extraProperties);
+ props.putAll(super.asMap());
+
+ Map map = new LinkedHashMap<>();
+ map.put(name, props);
+ return map;
+ }
+
+ public static Set supportedTools() {
+ Set set = new LinkedHashSet<>();
+ set.add(Brew.TOOL_NAME);
+ set.add(Chocolatey.TOOL_NAME);
+ set.add(Scoop.TOOL_NAME);
+ set.add(Snap.TOOL_NAME);
+ return Collections.unmodifiableSet(set);
+ }
+
+ public enum DistributionType {
+ BINARY,
+ SINGLE_JAR
+ // NATIVE_IMAGE,
+ }
+}
diff --git a/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/ExtraProperties.java b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/ExtraProperties.java
new file mode 100644
index 00000000..119b4b80
--- /dev/null
+++ b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/ExtraProperties.java
@@ -0,0 +1,33 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.model;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public interface ExtraProperties extends Serializable {
+ Map getExtraProperties();
+
+ void setExtraProperties(Map properties);
+
+ void addExtraProperties(Map properties);
+}
diff --git a/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/JReleaserModel.java b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/JReleaserModel.java
new file mode 100644
index 00000000..70a20e17
--- /dev/null
+++ b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/JReleaserModel.java
@@ -0,0 +1,104 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.model;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.kordamp.jreleaser.util.StringUtils.isBlank;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class JReleaserModel extends AbstractDomain {
+ private final Project project = new Project();
+ private final Release release = new Release();
+ private final Packagers packagers = new Packagers();
+ private final Map distributions = new LinkedHashMap<>();
+
+ public Project getProject() {
+ return project;
+ }
+
+ public void setProject(Project project) {
+ this.project.setAll(project);
+ }
+
+ public Release getRelease() {
+ return release;
+ }
+
+ public void setRelease(Release release) {
+ this.release.setAll(release);
+ }
+
+ public Packagers getPackagers() {
+ return packagers;
+ }
+
+ public void setPackagers(Packagers packagers) {
+ this.packagers.setAll(packagers);
+ }
+
+ public Map getDistributions() {
+ return distributions;
+ }
+
+ public void setDistributions(Map distributions) {
+ this.distributions.clear();
+ this.distributions.putAll(distributions);
+ }
+
+ public void addDistributions(Map distributions) {
+ this.distributions.putAll(distributions);
+ }
+
+ public void addDistribution(Distribution distribution) {
+ this.distributions.put(distribution.getName(), distribution);
+ }
+
+ public Distribution findDistribution(String name) {
+ if (isBlank(name)) {
+ throw new IllegalArgumentException("Distribution name must not be blank");
+ }
+
+ if (distributions.isEmpty()) {
+ throw new IllegalArgumentException("No distributions have been configured");
+ }
+
+ if (distributions.containsKey(name)) {
+ return distributions.get(name);
+ }
+
+ throw new IllegalArgumentException("Distribution '" + name + "' not found");
+ }
+
+ public Map asMap() {
+ Map map = new LinkedHashMap<>();
+ map.put("project", project.asMap());
+ map.put("release", release.asMap());
+ map.put("packagers", packagers.asMap());
+ map.put("distributions", distributions.values()
+ .stream()
+ .map(Distribution::asMap)
+ .collect(Collectors.toList()));
+ return map;
+ }
+}
diff --git a/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/JReleaserModelPrinter.java b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/JReleaserModelPrinter.java
new file mode 100644
index 00000000..8ef1a146
--- /dev/null
+++ b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/JReleaserModelPrinter.java
@@ -0,0 +1,298 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.model;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+
+import static org.kordamp.jreleaser.util.StringUtils.isNotBlank;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public abstract class JReleaserModelPrinter {
+ private static final String SECRET_KEYWORDS = "password,secret,credential,token,apikey,login";
+ private static final String KEY_SECRET_KEYWORDS = "kordamp.secret.keywords";
+ private final boolean showSecrets;
+ private final PrintWriter out;
+
+ public JReleaserModelPrinter(PrintWriter out) {
+ this(out, false);
+ }
+
+ public JReleaserModelPrinter(PrintWriter out, boolean showSecrets) {
+ this.out = out;
+ this.showSecrets = showSecrets;
+ }
+
+ public void print(Object value) {
+ print(value, 0);
+ }
+
+ public void print(Object value, int offset) {
+ if (value instanceof Map) {
+ doPrintMap((Map) value, offset);
+ } else if (value instanceof Collection) {
+ doPrintCollection((Collection>) value, offset);
+ } else if (null != value) {
+ Class> clazz = value.getClass();
+ if (clazz.isArray()) {
+ doPrintArray((Object[]) value, offset);
+ } else {
+ doPrintElement(value, offset);
+ }
+ }
+ }
+
+ private void print(String value, int offset) {
+ doPrintElement(value, offset);
+ }
+
+ private void doPrintMap(Map map, final int offset) {
+ if (map != null) {
+ map.forEach((key, value) -> {
+ if (value instanceof Map) {
+ if (!((Map) value).isEmpty()) {
+ out.println(multiply(" ", offset) + key + ":");
+ doPrintMap((Map) value, offset + 1);
+ }
+ } else if (value instanceof Collection) {
+ if (!((Collection) value).isEmpty()) {
+ out.println(multiply(" ", offset) + key + ":");
+ doPrintCollection((Collection) value, offset + 1);
+ }
+ } else if (null != value && value.getClass().isArray()) {
+ if (((Object[]) value).length > 0) {
+ out.println(multiply(" ", offset) + key + ":");
+ doPrintArray((Object[]) value, offset + 1);
+ }
+ } else if (isNotNullNorBlank(value)) {
+ doPrintMapEntry(key, value, offset);
+ }
+
+ if (offset == 0) {
+ out.println(" ");
+ }
+ });
+ }
+ }
+
+ private void doPrintMapEntry(String key, Object value, int offset) {
+ if (value instanceof Map) {
+ doPrintMap(key, (Map) value, offset);
+ } else if (value instanceof Collection) {
+ doPrintCollection(key, (Collection>) value, offset);
+ } else if (null != value) {
+ Class> clazz = value.getClass();
+ if (clazz.isArray()) {
+ doPrintArray(key, (Object[]) value, offset);
+ } else {
+ String result = formatValue(value, isSecret(key));
+ if (isNotNullNorBlank(result)) {
+ out.println(multiply(" ", offset) + key + ": " + result);
+ }
+ }
+ }
+ }
+
+ private void doPrintCollection(Collection> collection, final int offset) {
+ if (collection != null) {
+ collection.forEach(value -> {
+ if (value instanceof Map) {
+ if (!((Map) value).isEmpty()) {
+ doPrintMap((Map) value, offset);
+ }
+ } else if (value instanceof Collection) {
+ if (!((Collection) value).isEmpty()) {
+ doPrintCollection((Collection) value, offset + 1);
+ }
+ } else if (null != value && value.getClass().isArray()) {
+ if (((Object[]) value).length > 0) {
+ doPrintArray((Object[]) value, offset + 1);
+ }
+ } else if (isNotNullNorBlank(value)) {
+ doPrintElement(value, offset);
+ }
+ });
+ }
+ }
+
+ private void doPrintArray(Object[] array, final int offset) {
+ if (array != null) {
+ Arrays.stream(array).forEach(value -> {
+ if (value instanceof Map) {
+ if (!((Map) value).isEmpty()) {
+ doPrintMap((Map) value, offset);
+ }
+ } else if (value instanceof Collection) {
+ if (!((Collection) value).isEmpty()) {
+ doPrintCollection((Collection) value, offset + 1);
+ }
+ } else if (null != value && value.getClass().isArray()) {
+ if (((Object[]) value).length > 0) {
+ doPrintArray((Object[]) value, offset + 1);
+ }
+ } else if (isNotNullNorBlank(value)) {
+ doPrintElement(value, offset);
+ }
+ });
+ }
+ }
+
+ private void doPrintMap(String key, Map map, int offset) {
+ if (map != null && !map.isEmpty()) {
+ out.println(multiply(" ", offset) + key + ':');
+ doPrintMap(map, offset + 1);
+ }
+ }
+
+ private void doPrintCollection(String key, Collection> collection, int offset) {
+ if (collection != null && !collection.isEmpty()) {
+ out.println(multiply(" ", offset) + key + ':');
+ doPrintCollection(collection, offset + 1);
+ }
+ }
+
+ private void doPrintArray(String key, Object[] array, int offset) {
+ if (array != null && array.length > 0) {
+ out.println(multiply(" ", offset) + key + ':');
+ doPrintArray(array, offset + 1);
+ }
+ }
+
+ private void doPrintElement(Object value, int offset) {
+ String result = formatValue(value);
+ if (isNotNullNorBlank(result)) {
+ out.println(multiply(" ", offset) + result);
+ }
+ }
+
+ private boolean isNotNullNorBlank(Object value) {
+ if (value instanceof CharSequence) {
+ return isNotBlank(String.valueOf(value));
+ }
+
+ return value != null;
+ }
+
+ private String formatValue(Object value) {
+ return formatValue(value, false);
+ }
+
+ private String formatValue(Object value, boolean secret) {
+ if (value instanceof Boolean) {
+ Boolean b = (Boolean) value;
+ return b ? green(String.valueOf(b)) : red(String.valueOf(b));
+ } else if (value instanceof Number) {
+ return cyan(String.valueOf(value));
+ } else if (value != null) {
+ String s = String.valueOf(value);
+ s = secret && !showSecrets ? multiply("*", 12) : s;
+
+ String r = parseAsBoolean(s);
+ if (r != null) return r;
+ r = parseAsInteger(s);
+ if (r != null) return r;
+ r = parseAsDouble(s);
+ if (r != null) return r;
+
+ return secret ? magenta(s) : yellow(s);
+ }
+
+ return String.valueOf(value);
+ }
+
+ private String parseAsBoolean(String s) {
+ if ("true".equalsIgnoreCase(s) || "false".equalsIgnoreCase(s)) {
+ boolean b = Boolean.valueOf(s);
+ return b ? green(String.valueOf(b)) : red(String.valueOf(b));
+ } else {
+ return null;
+ }
+ }
+
+ private String parseAsInteger(String s) {
+ try {
+ Integer.parseInt(s);
+ return cyan(s);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private String parseAsDouble(String s) {
+ try {
+ Double.parseDouble(s);
+ return cyan(s);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private boolean isSecret(String key) {
+ String lower = key.toLowerCase();
+
+ for (String keyword : System.getProperty(KEY_SECRET_KEYWORDS, SECRET_KEYWORDS).split(",")) {
+ if (lower.contains(keyword.trim().toLowerCase())) return true;
+ }
+
+ return false;
+ }
+
+ private String cyan(String s) {
+ return color("cyan", s);
+ }
+
+ private String red(String s) {
+ return color("red", s);
+ }
+
+ private String green(String s) {
+ return color("green", s);
+ }
+
+ private String magenta(String s) {
+ return color("magenta", s);
+ }
+
+ private String yellow(String s) {
+ return color("yellow", s);
+ }
+
+ protected abstract String color(String color, String input);
+
+ private static String multiply(CharSequence self, Number factor) {
+ int size = factor.intValue();
+ if (size == 0) {
+ return "";
+ } else if (size < 0) {
+ throw new IllegalArgumentException("multiply() should be called with a number of 0 or greater not: " + size);
+ } else {
+ StringBuilder answer = new StringBuilder(self);
+
+ for (int i = 1; i < size; ++i) {
+ answer.append(self);
+ }
+
+ return answer.toString();
+ }
+ }
+}
diff --git a/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/JReleaserModelValidator.java b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/JReleaserModelValidator.java
new file mode 100644
index 00000000..e2c5f804
--- /dev/null
+++ b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/JReleaserModelValidator.java
@@ -0,0 +1,352 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.model;
+
+import org.kordamp.jreleaser.util.Logger;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static java.util.stream.Collectors.groupingBy;
+import static org.kordamp.jreleaser.util.StringUtils.capitalize;
+import static org.kordamp.jreleaser.util.StringUtils.getFilenameExtension;
+import static org.kordamp.jreleaser.util.StringUtils.isBlank;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public final class JReleaserModelValidator {
+ private JReleaserModelValidator() {
+ // noop
+ }
+
+ public static List validate(Logger logger, Path basedir, JReleaserModel model) {
+ List errors = new ArrayList<>();
+ validateModel(logger, basedir, model, errors);
+ return Collections.unmodifiableList(errors);
+ }
+
+ private static void validateModel(Logger logger, Path basedir, JReleaserModel model, List errors) {
+ validateProject(logger, basedir, model.getProject(), errors);
+ validateRelease(logger, basedir, model.getProject(), model.getRelease(), errors);
+ validateDistributions(logger, basedir, model, model.getDistributions(), errors);
+ }
+
+ private static void validateProject(Logger logger, Path basedir, Project project, List errors) {
+ if (isBlank(project.getName())) {
+ errors.add("project.name must not be blank");
+ }
+ if (isBlank(project.getVersion())) {
+ errors.add("project.version must not be blank");
+ }
+ if (isBlank(project.getDescription())) {
+ errors.add("project.description must not be blank");
+ }
+ if (isBlank(project.getWebsite())) {
+ errors.add("project.website must not be blank");
+ }
+ if (isBlank(project.getLicense())) {
+ errors.add("project.license must not be blank");
+ }
+ if (isBlank(project.getLongDescription())) {
+ project.setLongDescription(project.getDescription());
+ }
+ if (project.getAuthors().isEmpty()) {
+ errors.add("project.authors must not be empty");
+ }
+
+ adjustExtraProperties(project, "project");
+ }
+
+ private static void validateRelease(Logger logger, Path basedir, Project project, Release release, List errors) {
+ if (isBlank(release.getRepoOwner())) {
+ errors.add("release.repoOwner must not be blank");
+ }
+ if (isBlank(release.getRepoName())) {
+ release.setRepoName(project.getName());
+ }
+ if (null == release.getRepoType()) {
+ release.setRepoType(Release.RepoType.GITHUB);
+ }
+
+ if (isBlank(release.getRepoHost())) {
+ release.setRepoHost(release.getRepoType().repoHost());
+ }
+ if (isBlank(release.getDownloadUrlFormat())) {
+ release.setDownloadUrlFormat(release.getRepoType().downloadUrlFormat());
+ }
+ if (isBlank(release.getReleaseNotesUrlFormat())) {
+ release.setReleaseNotesUrlFormat(release.getRepoType().releaseNotesUrlFormat());
+ }
+ if (isBlank(release.getLatestReleaseUrlFormat())) {
+ release.setLatestReleaseUrlFormat(release.getRepoType().latestReleaseUrlFormat());
+ }
+ if (isBlank(release.getIssueTrackerUrlFormat())) {
+ release.setIssueTrackerUrlFormat(release.getRepoType().issueTrackerUrlFormat());
+ }
+
+ if (isBlank(release.getAuthorization())) {
+ String tokenName = release.getRepoType().name() + "_TOKEN";
+ logger.warn("release.auhorization is not explicitly defined. Checking environment for {}", tokenName);
+ if (isBlank(System.getenv(tokenName))) {
+ errors.add("release.authorization must not be blank. Alternatively define a " + tokenName + " environment variable.");
+ }
+ return;
+ }
+ if (isBlank(release.getTagName())) {
+ release.setTagName("v" + project.getVersion());
+ }
+ if (isBlank(release.getTargetCommitish())) {
+ release.setTargetCommitish("main");
+ }
+ }
+
+ private static void validateDistributions(Logger logger, Path basedir, JReleaserModel model, Map distributions, List errors) {
+ if (distributions.isEmpty()) {
+ errors.add("Missing distributions configuration");
+ return;
+ }
+
+ if (distributions.size() == 1) {
+ distributions.values().stream()
+ .findFirst().ifPresent(distribution -> distribution.setName(model.getProject().getName()));
+ }
+
+ for (Map.Entry e : distributions.entrySet()) {
+ Distribution distribution = e.getValue();
+ if (isBlank(distribution.getName())) {
+ distribution.setName(e.getKey());
+ }
+ validateDistribution(logger, basedir, model, distribution, errors);
+ }
+ }
+
+ private static void validateDistribution(Logger logger, Path basedir, JReleaserModel model, Distribution distribution, List errors) {
+ if (isBlank(distribution.getName())) {
+ errors.add("distribution.name must not be blank");
+ return;
+ }
+ if (null == distribution.getType()) {
+ errors.add("distribution." + distribution.getName() + ".type must not be null");
+ return;
+ }
+ if (isBlank(distribution.getExecutable())) {
+ distribution.setExecutable(distribution.getName());
+ }
+ if (isBlank(distribution.getJavaVersion())) {
+ distribution.setJavaVersion(model.getProject().getJavaVersion());
+ }
+ if (null == distribution.getArtifacts() || distribution.getArtifacts().isEmpty()) {
+ errors.add("distribution." + distribution.getName() + ".artifacts is empty");
+ return;
+ }
+
+ List tags = new ArrayList<>();
+ tags.addAll(model.getProject().getTags());
+ tags.addAll(distribution.getTags());
+ distribution.setTags(tags);
+
+ for (int i = 0; i < distribution.getArtifacts().size(); i++) {
+ validateArtifact(logger, basedir, model, distribution, distribution.getArtifacts().get(i), i, errors);
+ }
+ // validate artifact.osClassifier is unique
+ Map> byClassifier = distribution.getArtifacts().stream()
+ .collect(groupingBy(artifact -> isBlank(artifact.getOsClassifier()) ? "" : artifact.getOsClassifier()));
+ // check classifiers by extension
+ byClassifier.entrySet().forEach(c -> {
+ String classifier = "".equals(c.getKey()) ? "no" : c.getKey();
+ c.getValue().stream()
+ .collect(groupingBy(artifact -> getFilenameExtension(artifact.getPath())))
+ .entrySet().forEach(e -> {
+ if (e.getValue().size() > 1) {
+ errors.add("distribution." + distribution.getName() +
+ " has more than one artifact with " + classifier +
+ " classifier for extension " + e.getValue());
+ }
+ });
+ });
+
+ adjustExtraProperties(distribution, "distribution");
+
+ validateBrew(logger, basedir, model, distribution, distribution.getBrew(), errors);
+ validateChocolatey(logger, basedir, model, distribution, distribution.getChocolatey(), errors);
+ validateScoop(logger, basedir, model, distribution, distribution.getScoop(), errors);
+ validateSnap(logger, basedir, model, distribution, distribution.getSnap(), errors);
+ }
+
+ private static void validateArtifact(Logger logger, Path basedir, JReleaserModel model, Distribution distribution, Artifact artifact, int index, List errors) {
+ if (null == artifact) {
+ errors.add("distribution." + distribution.getName() + ".artifact[" + index + "] is null");
+ return;
+ }
+ if (isBlank(artifact.getPath())) {
+ errors.add("distribution." + distribution.getName() + ".artifact[" + index + "].path must not be null");
+ }
+ if (isBlank(artifact.getJavaVersion())) {
+ artifact.setJavaVersion(distribution.getJavaVersion());
+ }
+ }
+
+ private static void validateBrew(Logger logger, Path basedir, JReleaserModel model, Distribution distribution, Brew tool, List errors) {
+ if (!tool.isEnabledSet() && model.getPackagers().getBrew().isEnabledSet()) {
+ tool.setEnabled(model.getPackagers().getBrew().isEnabled());
+ }
+ validateTemplate(logger, model, distribution, tool, tool.getToolName(), errors);
+ adjustExtraProperties(model.getPackagers().getBrew(), tool.getToolName());
+ adjustExtraProperties(tool, tool.getToolName());
+ mergeExtraProperties(tool, model.getPackagers().getBrew());
+
+ Map dependencies = new LinkedHashMap<>(model.getPackagers().getBrew().getDependencies());
+ dependencies.putAll(tool.getDependencies());
+
+ tool.setDependencies(dependencies);
+ }
+
+ private static void validateChocolatey(Logger logger, Path basedir, JReleaserModel model, Distribution distribution, Chocolatey tool, List errors) {
+ if (!tool.isEnabledSet() && model.getPackagers().getChocolatey().isEnabledSet()) {
+ tool.setEnabled(model.getPackagers().getChocolatey().isEnabled());
+ }
+ validateTemplate(logger, model, distribution, tool, tool.getToolName(), errors);
+ adjustExtraProperties(model.getPackagers().getChocolatey(), tool.getToolName());
+ adjustExtraProperties(tool, tool.getToolName());
+ mergeExtraProperties(tool, model.getPackagers().getChocolatey());
+ }
+
+ private static void validateScoop(Logger logger, Path basedir, JReleaserModel model, Distribution distribution, Scoop tool, List errors) {
+ if (!tool.isEnabledSet() && model.getPackagers().getScoop().isEnabledSet()) {
+ tool.setEnabled(model.getPackagers().getScoop().isEnabled());
+ }
+ validateTemplate(logger, model, distribution, tool, tool.getToolName(), errors);
+ Scoop commonScoop = model.getPackagers().getScoop();
+ adjustExtraProperties(commonScoop, tool.getToolName());
+ adjustExtraProperties(tool, tool.getToolName());
+ mergeExtraProperties(tool, model.getPackagers().getScoop());
+
+ if (isBlank(tool.getCheckverUrl())) {
+ tool.setCheckverUrl(commonScoop.getCheckverUrl());
+ if (isBlank(tool.getCheckverUrl())) {
+ tool.setCheckverUrl(model.getRelease().getLatestReleaseUrlFormat());
+ }
+ }
+ if (isBlank(tool.getAutoupdateUrl())) {
+ tool.setAutoupdateUrl(commonScoop.getAutoupdateUrl());
+ if (isBlank(tool.getAutoupdateUrl())) {
+ tool.setAutoupdateUrl(model.getRelease().getDownloadUrlFormat());
+ }
+ }
+ }
+
+ private static void validateSnap(Logger logger, Path basedir, JReleaserModel model, Distribution distribution, Snap tool, List errors) {
+ if (!tool.isEnabledSet() && model.getPackagers().getSnap().isEnabledSet()) {
+ tool.setEnabled(model.getPackagers().getSnap().isEnabled());
+ }
+ validateTemplate(logger, model, distribution, tool, tool.getToolName(), errors);
+ Snap commonSnap = model.getPackagers().getSnap();
+ adjustExtraProperties(commonSnap, tool.getToolName());
+ adjustExtraProperties(tool, tool.getToolName());
+ mergeExtraProperties(tool, model.getPackagers().getSnap());
+ mergeSnapPlugs(tool, model.getPackagers().getSnap());
+ mergeSnapSlots(tool, model.getPackagers().getSnap());
+
+ if (isBlank(tool.getBase())) {
+ tool.setBase(commonSnap.getBase());
+ if (isBlank(tool.getBase())) {
+ errors.add("distribution." + distribution.getName() + ".snap.base must not be blank");
+ }
+ }
+ if (isBlank(tool.getGrade())) {
+ tool.setGrade(commonSnap.getGrade());
+ if (isBlank(tool.getGrade())) {
+ errors.add("distribution." + distribution.getName() + ".snap.grade must not be blank");
+ }
+ }
+ if (isBlank(tool.getConfinement())) {
+ tool.setConfinement(commonSnap.getConfinement());
+ if (isBlank(tool.getConfinement())) {
+ errors.add("distribution." + distribution.getName() + ".snap.confinement must not be blank");
+ }
+ }
+ if (isBlank(tool.getExportedLogin())) {
+ tool.setExportedLogin(commonSnap.getExportedLogin());
+ if (isBlank(tool.getExportedLogin())) {
+ errors.add("distribution." + distribution.getName() + ".snap.exportedLogin must not be empty");
+ } else if (!basedir.resolve(tool.getExportedLogin()).toFile().exists()) {
+ errors.add("distribution." + distribution.getName() + ".snap.exportedLogin does not exist. " + basedir.resolve(tool.getExportedLogin()));
+ }
+ }
+ }
+
+ private static void mergeExtraProperties(Tool tool, Tool common) {
+ Map extraProperties = new LinkedHashMap<>(common.getExtraProperties());
+ extraProperties.putAll(tool.getExtraProperties());
+ tool.setExtraProperties(extraProperties);
+ }
+
+ private static void mergeSnapPlugs(Snap tool, Snap common) {
+ Map commonPlugs = common.getPlugs().stream()
+ .collect(Collectors.toMap(Plug::getName, Plug::copyOf));
+ Map toolPlugs = tool.getPlugs().stream()
+ .collect(Collectors.toMap(Plug::getName, Plug::copyOf));
+ commonPlugs.forEach((name, cp) -> {
+ Plug tp = toolPlugs.remove(name);
+ if (null != tp) {
+ cp.getAttributes().putAll(tp.getAttributes());
+ }
+ });
+ commonPlugs.putAll(toolPlugs);
+ tool.setPlugs(new ArrayList<>(commonPlugs.values()));
+ }
+
+ private static void mergeSnapSlots(Snap tool, Snap common) {
+ Map commonSlots = common.getSlots().stream()
+ .collect(Collectors.toMap(Slot::getName, Slot::copyOf));
+ Map toolSlots = tool.getSlots().stream()
+ .collect(Collectors.toMap(Slot::getName, Slot::copyOf));
+ commonSlots.forEach((name, cp) -> {
+ Slot tp = toolSlots.remove(name);
+ if (null != tp) {
+ cp.getAttributes().putAll(tp.getAttributes());
+ }
+ });
+ commonSlots.putAll(toolSlots);
+ tool.setSlots(new ArrayList<>(commonSlots.values()));
+ }
+
+ private static void validateTemplate(Logger logger, JReleaserModel model, Distribution distribution, Tool tool, String toolName, List errors) {
+ if (null != tool.getTemplateDirectory() && !tool.getTemplateDirectory().toFile().exists()) {
+ errors.add("distribution." + distribution.getName() + "." + toolName + ".template does not exist. " + tool.getTemplateDirectory());
+ }
+ }
+
+ private static void adjustExtraProperties(ExtraProperties extra, String prefix) {
+ if (extra.getExtraProperties().size() > 0) {
+ Map props = extra.getExtraProperties()
+ .entrySet().stream()
+ .collect(Collectors.toMap(
+ e -> prefix + capitalize(e.getKey()),
+ Map.Entry::getValue));
+ extra.setExtraProperties(props);
+ }
+ }
+}
diff --git a/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Packagers.java b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Packagers.java
new file mode 100644
index 00000000..ae6d4556
--- /dev/null
+++ b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Packagers.java
@@ -0,0 +1,81 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.model;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class Packagers extends AbstractDomain {
+ private final Brew brew = new Brew();
+ private final Chocolatey chocolatey = new Chocolatey();
+ private final Scoop scoop = new Scoop();
+ private final Snap snap = new Snap();
+
+ void setAll(Packagers packagers) {
+ setBrew(packagers.brew);
+ setChocolatey(packagers.chocolatey);
+ setScoop(packagers.scoop);
+ setSnap(packagers.snap);
+ }
+
+ public Brew getBrew() {
+ return brew;
+ }
+
+ public void setBrew(Brew brew) {
+ this.brew.setAll(brew);
+ }
+
+ public Chocolatey getChocolatey() {
+ return chocolatey;
+ }
+
+ public void setChocolatey(Chocolatey chocolatey) {
+ this.chocolatey.setAll(chocolatey);
+ }
+
+ public Scoop getScoop() {
+ return scoop;
+ }
+
+ public void setScoop(Scoop scoop) {
+ this.scoop.setAll(scoop);
+ }
+
+ public Snap getSnap() {
+ return snap;
+ }
+
+ public void setSnap(Snap snap) {
+ this.snap.setAll(snap);
+ }
+
+ @Override
+ public Map asMap() {
+ Map map = new LinkedHashMap<>();
+ map.putAll(brew.asMap());
+ map.putAll(chocolatey.asMap());
+ map.putAll(scoop.asMap());
+ map.putAll(snap.asMap());
+ return map;
+ }
+}
diff --git a/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Plug.java b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Plug.java
new file mode 100644
index 00000000..b314e7dc
--- /dev/null
+++ b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Plug.java
@@ -0,0 +1,68 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.model;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class Plug extends AbstractDomain {
+ private final Map attributes = new LinkedHashMap<>();
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Map getAttributes() {
+ return attributes;
+ }
+
+ public void setAttributes(Map attributes) {
+ this.attributes.clear();
+ this.attributes.putAll(attributes);
+ }
+
+ public void addAttributes(Map attributes) {
+ this.attributes.putAll(attributes);
+ }
+
+ public void addAttribute(String key, String value) {
+ attributes.put(key, value);
+ }
+
+ public Map asMap() {
+ Map map = new LinkedHashMap<>();
+ map.put(name, attributes);
+ return map;
+ }
+
+ public static Plug copyOf(Plug other) {
+ Plug copy = new Plug();
+ copy.setName(other.getName());
+ copy.setAttributes(other.getAttributes());
+ return copy;
+ }
+}
diff --git a/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Project.java b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Project.java
new file mode 100644
index 00000000..ec7472d9
--- /dev/null
+++ b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Project.java
@@ -0,0 +1,192 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.model;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.kordamp.jreleaser.util.StringUtils.isNotBlank;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class Project extends AbstractDomain implements ExtraProperties {
+ private final List authors = new ArrayList<>();
+ private final List tags = new ArrayList<>();
+ private final Map extraProperties = new LinkedHashMap<>();
+ private String name;
+ private String version;
+ private String description;
+ private String longDescription;
+ private String website;
+ private String license;
+ private String javaVersion;
+
+ void setAll(Project project) {
+ this.name = project.name;
+ this.version = project.version;
+ this.description = project.description;
+ this.longDescription = project.longDescription;
+ this.website = project.website;
+ this.license = project.license;
+ this.javaVersion = project.javaVersion;
+ setAuthors(project.authors);
+ setTags(project.tags);
+ setExtraProperties(project.extraProperties);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getLongDescription() {
+ return longDescription;
+ }
+
+ public void setLongDescription(String longDescription) {
+ this.longDescription = longDescription;
+ }
+
+ public String getWebsite() {
+ return website;
+ }
+
+ public void setWebsite(String website) {
+ this.website = website;
+ }
+
+ public String getLicense() {
+ return license;
+ }
+
+ public void setLicense(String license) {
+ this.license = license;
+ }
+
+ public String getJavaVersion() {
+ return javaVersion;
+ }
+
+ public void setJavaVersion(String javaVersion) {
+ this.javaVersion = javaVersion;
+ }
+
+ @Override
+ public Map getExtraProperties() {
+ return extraProperties;
+ }
+
+ @Override
+ public void setExtraProperties(Map extraProperties) {
+ this.extraProperties.putAll(extraProperties);
+ }
+
+ @Override
+ public void addExtraProperties(Map extraProperties) {
+ this.extraProperties.putAll(extraProperties);
+ }
+
+ public List getAuthors() {
+ return authors;
+ }
+
+ public void setAuthors(List authors) {
+ this.authors.clear();
+ this.authors.addAll(authors);
+ }
+
+ public void addAuthors(List authors) {
+ this.authors.addAll(authors);
+ }
+
+ public void addAuthor(String author) {
+ if (isNotBlank(author)) {
+ this.authors.add(author.trim());
+ }
+ }
+
+ public void removeAuthor(String author) {
+ if (isNotBlank(author)) {
+ this.authors.remove(author.trim());
+ }
+ }
+
+ public List getTags() {
+ return tags;
+ }
+
+ public void setTags(List tags) {
+ this.tags.clear();
+ this.tags.addAll(tags);
+ }
+
+ public void addTags(List tags) {
+ this.tags.addAll(tags);
+ }
+
+ public void addTag(String tag) {
+ if (isNotBlank(tag)) {
+ this.tags.add(tag.trim());
+ }
+ }
+
+ public void removeTag(String tag) {
+ if (isNotBlank(tag)) {
+ this.tags.remove(tag.trim());
+ }
+ }
+
+ @Override
+ public Map asMap() {
+ Map map = new LinkedHashMap<>();
+ map.put("name", name);
+ map.put("version", version);
+ map.put("description", description);
+ map.put("longDescription", longDescription);
+ map.put("website", website);
+ map.put("license", license);
+ if (isNotBlank(javaVersion)) map.put("javaVersion", javaVersion);
+ map.put("authors", authors);
+ map.put("tags", tags);
+ map.put("extraProperties", extraProperties);
+ return map;
+ }
+}
diff --git a/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Release.java b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Release.java
new file mode 100644
index 00000000..4e4c135b
--- /dev/null
+++ b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Release.java
@@ -0,0 +1,296 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.model;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static org.kordamp.jreleaser.util.StringUtils.isNotBlank;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class Release extends AbstractDomain {
+ private RepoType repoType;
+ private String repoHost;
+ private String repoOwner;
+ private String repoName;
+ private String downloadUrlFormat;
+ private String releaseNotesUrlFormat;
+ private String latestReleaseUrlFormat;
+ private String issueTrackerUrlFormat;
+ private String authorization;
+ private String tagName;
+ private String targetCommitish = "main";
+ private String releaseName;
+ private String body = "";
+ private boolean draft;
+ private boolean prerelease;
+ private boolean overwrite;
+ private boolean allowUploadToExisting;
+ private String apiEndpoint;
+
+ void setAll(Release release) {
+ this.repoType = release.repoType;
+ this.repoHost = release.repoHost;
+ this.repoOwner = release.repoOwner;
+ this.repoName = release.repoName;
+ this.downloadUrlFormat = release.downloadUrlFormat;
+ this.releaseNotesUrlFormat = release.releaseNotesUrlFormat;
+ this.latestReleaseUrlFormat = release.latestReleaseUrlFormat;
+ this.issueTrackerUrlFormat = release.issueTrackerUrlFormat;
+ this.authorization = release.authorization;
+ this.tagName = release.tagName;
+ this.targetCommitish = release.targetCommitish;
+ this.releaseName = release.releaseName;
+ this.body = release.body;
+ this.draft = release.draft;
+ this.prerelease = release.prerelease;
+ this.overwrite = release.overwrite;
+ this.allowUploadToExisting = release.allowUploadToExisting;
+ this.apiEndpoint = release.apiEndpoint;
+ }
+
+ public RepoType getRepoType() {
+ return repoType;
+ }
+
+ public void setRepoType(RepoType repoType) {
+ this.repoType = repoType;
+ }
+
+ public void setRepoType(String repoType) {
+ if (isNotBlank(repoType)) {
+ this.repoType = RepoType.valueOf(repoType.toUpperCase());
+ }
+ }
+
+ public String getRepoHost() {
+ return repoHost;
+ }
+
+ public void setRepoHost(String repoHost) {
+ this.repoHost = repoHost;
+ }
+
+ public String getRepoOwner() {
+ return repoOwner;
+ }
+
+ public void setRepoOwner(String repoOwner) {
+ this.repoOwner = repoOwner;
+ }
+
+ public String getRepoName() {
+ return repoName;
+ }
+
+ public void setRepoName(String repoName) {
+ this.repoName = repoName;
+ }
+
+ public String getDownloadUrlFormat() {
+ return downloadUrlFormat;
+ }
+
+ public void setDownloadUrlFormat(String downloadUrlFormat) {
+ this.downloadUrlFormat = downloadUrlFormat;
+ }
+
+ public String getReleaseNotesUrlFormat() {
+ return releaseNotesUrlFormat;
+ }
+
+ public void setReleaseNotesUrlFormat(String releaseNotesUrlFormat) {
+ this.releaseNotesUrlFormat = releaseNotesUrlFormat;
+ }
+
+ public String getLatestReleaseUrlFormat() {
+ return latestReleaseUrlFormat;
+ }
+
+ public void setLatestReleaseUrlFormat(String latestReleaseUrlFormat) {
+ this.latestReleaseUrlFormat = latestReleaseUrlFormat;
+ }
+
+ public String getIssueTrackerUrlFormat() {
+ return issueTrackerUrlFormat;
+ }
+
+ public void setIssueTrackerUrlFormat(String issueTrackerUrlFormat) {
+ this.issueTrackerUrlFormat = issueTrackerUrlFormat;
+ }
+
+ public String getAuthorization() {
+ return authorization;
+ }
+
+ public void setAuthorization(String authorization) {
+ this.authorization = authorization;
+ }
+
+ public String getTagName() {
+ return tagName;
+ }
+
+ public void setTagName(String tagName) {
+ this.tagName = tagName;
+ }
+
+ public String getTargetCommitish() {
+ return targetCommitish;
+ }
+
+ public void setTargetCommitish(String targetCommitish) {
+ this.targetCommitish = targetCommitish;
+ }
+
+ public String getReleaseName() {
+ return releaseName;
+ }
+
+ public void setReleaseName(String releaseName) {
+ this.releaseName = releaseName;
+ }
+
+ public String getBody() {
+ return body;
+ }
+
+ public void setBody(String body) {
+ this.body = body;
+ }
+
+ public boolean isDraft() {
+ return draft;
+ }
+
+ public void setDraft(boolean draft) {
+ this.draft = draft;
+ }
+
+ public boolean isPrerelease() {
+ return prerelease;
+ }
+
+ public void setPrerelease(boolean prerelease) {
+ this.prerelease = prerelease;
+ }
+
+ public boolean isOverwrite() {
+ return overwrite;
+ }
+
+ public void setOverwrite(boolean overwrite) {
+ this.overwrite = overwrite;
+ }
+
+ public boolean isAllowUploadToExisting() {
+ return allowUploadToExisting;
+ }
+
+ public void setAllowUploadToExisting(boolean allowUploadToExisting) {
+ this.allowUploadToExisting = allowUploadToExisting;
+ }
+
+ public String getApiEndpoint() {
+ return apiEndpoint;
+ }
+
+ public void setApiEndpoint(String apiEndpoint) {
+ this.apiEndpoint = apiEndpoint;
+ }
+
+ @Override
+ public Map asMap() {
+ Map map = new LinkedHashMap<>();
+ map.put("repoHost", repoHost);
+ map.put("repoType", repoType);
+ map.put("repoOwner", repoOwner);
+ map.put("repoName", repoName);
+ map.put("downloadUrlFormat", downloadUrlFormat);
+ map.put("releaseNotesUrlFormat", releaseNotesUrlFormat);
+ map.put("latestReleaseUrlFormat", latestReleaseUrlFormat);
+ map.put("issueTrackerUrlFormat", issueTrackerUrlFormat);
+ map.put("authorization", authorization);
+ map.put("tagName", tagName);
+ map.put("targetCommitish", targetCommitish);
+ map.put("releaseName", releaseName);
+ map.put("body ", isNotBlank(body));
+ map.put("draft", draft);
+ map.put("prerelease", prerelease);
+ map.put("overwrite", overwrite);
+ map.put("allowUploadToExisting", allowUploadToExisting);
+ map.put("apiEndpoint", apiEndpoint);
+ return map;
+ }
+
+ public enum RepoType {
+ GITHUB("github.com",
+ "https://{{repoHost}}/{{repoOwner}}/{{repoName}}/releases/download/v{{projectVersion}}/{{artifactFileName}}",
+ "https://{{repoHost}}/{{repoOwner}}/{{repoName}}/releases/tag/v{{projectVersion}}",
+ "https://{{repoHost}}/{{repoOwner}}/{{repoName}}/releases/latest",
+ "https://{{repoHost}}/{{repoOwner}}/{{repoName}}/issues");
+ /*
+ GITLAB("gitlab.com",
+ "https://{{repoHost}}/{{repoOwner}}/{{repoName}}/-/archive/v{{projectVersion}}/{{artifactFileName}}",
+ "https://{{repoHost}}/{{repoOwner}}/{{repoName}}/-/releases/v{{projectVersion}}",
+ "https://{{repoHost}}/{{repoOwner}}/{{repoName}}/-/releases/v{{projectVersion}}",
+ "https://{{repoHost}}/{{repoOwner}}/{{repoName}}/-/issues");
+ */
+
+ private final String repoHost;
+ private final String downloadUrlFormat;
+ private final String releaseNotesUrlFormat;
+ private final String latestReleaseUrlFormat;
+ private final String issueTrackerUrlFormat;
+
+ RepoType(String repoHost,
+ String downloadUrlFormat,
+ String releaseNotesUrlFormat,
+ String latestReleaseUrlFormat,
+ String issueTrackerUrlFormat) {
+ this.repoHost = repoHost;
+ this.downloadUrlFormat = downloadUrlFormat;
+ this.releaseNotesUrlFormat = releaseNotesUrlFormat;
+ this.latestReleaseUrlFormat = latestReleaseUrlFormat;
+ this.issueTrackerUrlFormat = issueTrackerUrlFormat;
+ }
+
+ String repoHost() {
+ return this.repoHost;
+ }
+
+ String downloadUrlFormat() {
+ return this.downloadUrlFormat;
+ }
+
+ String releaseNotesUrlFormat() {
+ return this.releaseNotesUrlFormat;
+ }
+
+ String latestReleaseUrlFormat() {
+ return this.latestReleaseUrlFormat;
+ }
+
+ String issueTrackerUrlFormat() {
+ return this.issueTrackerUrlFormat;
+ }
+ }
+}
diff --git a/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Scoop.java b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Scoop.java
new file mode 100644
index 00000000..acea1cd7
--- /dev/null
+++ b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Scoop.java
@@ -0,0 +1,63 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.model;
+
+import java.util.Map;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class Scoop extends AbstractTool {
+ public static final String TOOL_NAME = "scoop";
+
+ private String checkverUrl;
+ private String autoupdateUrl;
+
+ public Scoop() {
+ super(TOOL_NAME);
+ }
+
+ void setAll(Scoop scoop) {
+ super.setAll(scoop);
+ this.checkverUrl = scoop.checkverUrl;
+ this.autoupdateUrl = scoop.autoupdateUrl;
+ }
+
+ public String getCheckverUrl() {
+ return checkverUrl;
+ }
+
+ public void setCheckverUrl(String checkverUrl) {
+ this.checkverUrl = checkverUrl;
+ }
+
+ public String getAutoupdateUrl() {
+ return autoupdateUrl;
+ }
+
+ public void setAutoupdateUrl(String autoupdateUrl) {
+ this.autoupdateUrl = autoupdateUrl;
+ }
+
+ @Override
+ protected void asMap(Map props) {
+ props.put("checkverUrl", checkverUrl);
+ props.put("autoupdateUrl", autoupdateUrl);
+ }
+}
diff --git a/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Slot.java b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Slot.java
new file mode 100644
index 00000000..54e758f5
--- /dev/null
+++ b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Slot.java
@@ -0,0 +1,136 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.model;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.kordamp.jreleaser.util.StringUtils.isNotBlank;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class Slot extends AbstractDomain {
+ private final Map attributes = new LinkedHashMap<>();
+ private final List reads = new ArrayList<>();
+ private final List writes = new ArrayList<>();
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Map getAttributes() {
+ return attributes;
+ }
+
+ public void setAttributes(Map attributes) {
+ this.attributes.clear();
+ this.attributes.putAll(attributes);
+ }
+
+ public void addAttributes(Map attributes) {
+ this.attributes.putAll(attributes);
+ }
+
+ public void addAttribute(String key, String value) {
+ attributes.put(key, value);
+ }
+
+ public List getReads() {
+ return reads;
+ }
+
+ public void setReads(List reads) {
+ this.reads.clear();
+ this.reads.addAll(reads);
+ }
+
+ public void addReads(List read) {
+ this.reads.addAll(read);
+ }
+
+ public void addRead(String read) {
+ if (isNotBlank(read)) {
+ this.reads.add(read.trim());
+ }
+ }
+
+ public void removeRead(String read) {
+ if (isNotBlank(read)) {
+ this.reads.remove(read.trim());
+ }
+ }
+
+ public List getWrites() {
+ return writes;
+ }
+
+ public void setWrites(List writes) {
+ this.writes.clear();
+ this.writes.addAll(writes);
+ }
+
+ public void addWrites(List write) {
+ this.writes.addAll(write);
+ }
+
+ public void addWrite(String write) {
+ if (isNotBlank(write)) {
+ this.writes.add(write.trim());
+ }
+ }
+
+ public void removeWrite(String write) {
+ if (isNotBlank(write)) {
+ this.writes.remove(write.trim());
+ }
+ }
+
+ public boolean getHasReads() {
+ return !reads.isEmpty();
+ }
+
+ public boolean getHasWrites() {
+ return !writes.isEmpty();
+ }
+
+ public Map asMap() {
+ Map map = new LinkedHashMap<>();
+ map.put(name, attributes);
+ map.put("read", reads);
+ map.put("write", writes);
+ return map;
+ }
+
+ public static Slot copyOf(Slot other) {
+ Slot copy = new Slot();
+ copy.setName(other.getName());
+ copy.setAttributes(other.getAttributes());
+ copy.setReads(other.getReads());
+ copy.setWrites(other.getWrites());
+ return copy;
+ }
+}
diff --git a/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Snap.java b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Snap.java
new file mode 100644
index 00000000..aea1a51e
--- /dev/null
+++ b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Snap.java
@@ -0,0 +1,172 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.model;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static org.kordamp.jreleaser.util.StringUtils.isNotBlank;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class Snap extends AbstractTool {
+ public static final String TOOL_NAME = "snap";
+
+ private final List localPlugs = new ArrayList<>();
+ private final List plugs = new ArrayList<>();
+ private final List slots = new ArrayList<>();
+ private String base = "core18";
+ private String grade = "stable";
+ private String confinement = "strict";
+ private String exportedLogin;
+
+ public Snap() {
+ super(TOOL_NAME);
+ }
+
+ void setAll(Snap snap) {
+ super.setAll(snap);
+ this.base = snap.base;
+ this.grade = snap.grade;
+ this.confinement = snap.confinement;
+ this.exportedLogin = snap.exportedLogin;
+ setLocalPlugs(localPlugs);
+ setPlugs(plugs);
+ setSlots(slots);
+ }
+
+ public String getBase() {
+ return base;
+ }
+
+ public void setBase(String base) {
+ this.base = base;
+ }
+
+ public String getGrade() {
+ return grade;
+ }
+
+ public void setGrade(String grade) {
+ this.grade = grade;
+ }
+
+ public String getConfinement() {
+ return confinement;
+ }
+
+ public void setConfinement(String confinement) {
+ this.confinement = confinement;
+ }
+
+ public List getLocalPlugs() {
+ return localPlugs;
+ }
+
+ public void setLocalPlugs(List localPlugs) {
+ this.localPlugs.clear();
+ this.localPlugs.addAll(localPlugs);
+ }
+
+ public void addLocalPlugs(List localPlugs) {
+ this.localPlugs.addAll(localPlugs);
+ }
+
+ public void addLocalPlug(String localPlug) {
+ if (isNotBlank(localPlug)) {
+ this.localPlugs.add(localPlug.trim());
+ }
+ }
+
+ public void removeLocalPlug(String localPlug) {
+ if (isNotBlank(localPlug)) {
+ this.localPlugs.remove(localPlug.trim());
+ }
+ }
+
+ public List getPlugs() {
+ return plugs;
+ }
+
+ public void setPlugs(List plugs) {
+ this.plugs.clear();
+ this.plugs.addAll(plugs);
+ }
+
+ public void addPlugs(List plugs) {
+ this.plugs.addAll(plugs);
+ }
+
+ public void addPlug(Plug plug) {
+ if (null != plug) {
+ this.plugs.add(plug);
+ }
+ }
+
+ public void removePlug(Plug plug) {
+ if (null != plug) {
+ this.plugs.remove(plug);
+ }
+ }
+
+ public List getSlots() {
+ return slots;
+ }
+
+ public void setSlots(List slots) {
+ this.slots.clear();
+ this.slots.addAll(slots);
+ }
+
+ public void addSlots(List slots) {
+ this.slots.addAll(slots);
+ }
+
+ public void addSlot(Slot slot) {
+ if (null != slot) {
+ this.slots.add(slot);
+ }
+ }
+
+ public void removeSlot(Slot slot) {
+ if (null != slot) {
+ this.slots.remove(slot);
+ }
+ }
+ public String getExportedLogin() {
+ return exportedLogin;
+ }
+
+ public void setExportedLogin(String exportedLogin) {
+ this.exportedLogin = exportedLogin;
+ }
+
+ @Override
+ protected void asMap(Map props) {
+ props.put("base", base);
+ props.put("grade", grade);
+ props.put("confinement", confinement);
+ props.put("exportedLogin", exportedLogin);
+ props.put("localPlugs", localPlugs);
+ props.put("plugs", plugs);
+ props.put("slots", slots);
+ }
+}
diff --git a/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Tool.java b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Tool.java
new file mode 100644
index 00000000..d7227568
--- /dev/null
+++ b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/Tool.java
@@ -0,0 +1,39 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.model;
+
+import java.io.Serializable;
+import java.nio.file.Path;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public interface Tool extends Serializable, ExtraProperties {
+ boolean isEnabledSet();
+
+ Boolean isEnabled();
+
+ void setEnabled(Boolean enabled);
+
+ String getToolName();
+
+ Path getTemplateDirectory();
+
+ void setTemplateDirectory(Path templateDirectory);
+}
diff --git a/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/releaser/ReleaseException.java b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/releaser/ReleaseException.java
new file mode 100644
index 00000000..a57f4b0d
--- /dev/null
+++ b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/releaser/ReleaseException.java
@@ -0,0 +1,36 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.model.releaser;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class ReleaseException extends Exception {
+ public ReleaseException(String message) {
+ super(message);
+ }
+
+ public ReleaseException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ReleaseException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/releaser/Releaser.java b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/releaser/Releaser.java
new file mode 100644
index 00000000..301cd4c7
--- /dev/null
+++ b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/releaser/Releaser.java
@@ -0,0 +1,26 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.model.releaser;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public interface Releaser {
+ void release() throws ReleaseException;
+}
diff --git a/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/releaser/ReleaserBuilder.java b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/releaser/ReleaserBuilder.java
new file mode 100644
index 00000000..d40aa757
--- /dev/null
+++ b/core/jreleaser-model/src/main/java/org/kordamp/jreleaser/model/releaser/ReleaserBuilder.java
@@ -0,0 +1,30 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.model.releaser;
+
+import org.kordamp.jreleaser.model.JReleaserModel;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public interface ReleaserBuilder {
+ R build();
+
+ R buildFromModel(JReleaserModel model);
+}
diff --git a/core/jreleaser-model/src/main/module/module-info.java b/core/jreleaser-model/src/main/module/module-info.java
new file mode 100644
index 00000000..a7e335cb
--- /dev/null
+++ b/core/jreleaser-model/src/main/module/module-info.java
@@ -0,0 +1,22 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+module org.kordamp.jreleaser.model {
+ exports org.kordamp.jreleaser.model;
+ requires org.kordamp.jreleaser.util;
+}
\ No newline at end of file
diff --git a/core/jreleaser-releaser/gradle.properties b/core/jreleaser-releaser/gradle.properties
new file mode 100644
index 00000000..46430532
--- /dev/null
+++ b/core/jreleaser-releaser/gradle.properties
@@ -0,0 +1,19 @@
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Copyright 2020 Andres Almiray.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+project_description = JReleaser Releaser
\ No newline at end of file
diff --git a/core/jreleaser-releaser/jreleaser-releaser.gradle b/core/jreleaser-releaser/jreleaser-releaser.gradle
new file mode 100644
index 00000000..aaed5740
--- /dev/null
+++ b/core/jreleaser-releaser/jreleaser-releaser.gradle
@@ -0,0 +1,23 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+dependencies {
+ api project(':jreleaser-model')
+ api project(':jreleaser-templates')
+ api project(':github-java-sdk')
+}
\ No newline at end of file
diff --git a/core/jreleaser-releaser/src/main/java/org/kordamp/jreleaser/releaser/Releasers.java b/core/jreleaser-releaser/src/main/java/org/kordamp/jreleaser/releaser/Releasers.java
new file mode 100644
index 00000000..440f4e1e
--- /dev/null
+++ b/core/jreleaser-releaser/src/main/java/org/kordamp/jreleaser/releaser/Releasers.java
@@ -0,0 +1,38 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.releaser;
+
+import org.kordamp.jreleaser.model.JReleaserModel;
+import org.kordamp.jreleaser.model.releaser.ReleaserBuilder;
+import org.kordamp.jreleaser.sdk.github.GithubReleaser;
+import org.kordamp.jreleaser.util.Logger;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class Releasers {
+ public static RB findReleaser(Logger logger, JReleaserModel model) {
+ switch (model.getRelease().getRepoType()) {
+ case GITHUB:
+ return (RB) GithubReleaser.builder();
+ default:
+ throw new IllegalArgumentException("Unsupported releaser " + model.getRelease().getRepoType());
+ }
+ }
+}
diff --git a/core/jreleaser-releaser/src/main/module/module-info.java b/core/jreleaser-releaser/src/main/module/module-info.java
new file mode 100644
index 00000000..d03f3d22
--- /dev/null
+++ b/core/jreleaser-releaser/src/main/module/module-info.java
@@ -0,0 +1,23 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+module org.kordamp.jreleaser.releaser {
+ exports org.kordamp.jreleaser.releaser;
+ requires org.kordamp.jreleaser.model;
+ requires org.kordamp.jreleaser.util;
+}
\ No newline at end of file
diff --git a/core/jreleaser-templates/gradle.properties b/core/jreleaser-templates/gradle.properties
new file mode 100644
index 00000000..8e4d16cb
--- /dev/null
+++ b/core/jreleaser-templates/gradle.properties
@@ -0,0 +1,19 @@
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Copyright 2020 Andres Almiray.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+project_description = JReleaser tool templates
\ No newline at end of file
diff --git a/core/jreleaser-templates/jreleaser-templates.gradle b/core/jreleaser-templates/jreleaser-templates.gradle
new file mode 100644
index 00000000..7f66fd10
--- /dev/null
+++ b/core/jreleaser-templates/jreleaser-templates.gradle
@@ -0,0 +1,22 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+dependencies {
+ api project(':jreleaser-model')
+ api "com.github.spullara.mustache.java:compiler:$mustacheVersion"
+}
\ No newline at end of file
diff --git a/core/jreleaser-templates/src/main/java/org/kordamp/jreleaser/templates/TemplateGenerationException.java b/core/jreleaser-templates/src/main/java/org/kordamp/jreleaser/templates/TemplateGenerationException.java
new file mode 100644
index 00000000..35d3905a
--- /dev/null
+++ b/core/jreleaser-templates/src/main/java/org/kordamp/jreleaser/templates/TemplateGenerationException.java
@@ -0,0 +1,36 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.templates;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class TemplateGenerationException extends Exception {
+ public TemplateGenerationException(String message) {
+ super(message);
+ }
+
+ public TemplateGenerationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public TemplateGenerationException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/core/jreleaser-templates/src/main/java/org/kordamp/jreleaser/templates/TemplateGenerator.java b/core/jreleaser-templates/src/main/java/org/kordamp/jreleaser/templates/TemplateGenerator.java
new file mode 100644
index 00000000..641e3686
--- /dev/null
+++ b/core/jreleaser-templates/src/main/java/org/kordamp/jreleaser/templates/TemplateGenerator.java
@@ -0,0 +1,179 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.templates;
+
+import org.kordamp.jreleaser.model.Distribution;
+import org.kordamp.jreleaser.util.Logger;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.Scanner;
+
+import static java.nio.file.StandardOpenOption.CREATE;
+import static java.nio.file.StandardOpenOption.CREATE_NEW;
+import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
+import static java.nio.file.StandardOpenOption.WRITE;
+import static java.util.Objects.requireNonNull;
+import static org.kordamp.jreleaser.util.StringUtils.requireNonBlank;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class TemplateGenerator {
+ private final Logger logger;
+ private final String distributionName;
+ private final Distribution.DistributionType distributionType;
+ private final String toolName;
+ private final Path outputDirectory;
+ private final boolean overwrite;
+
+ private TemplateGenerator(Logger logger,
+ String distributionName,
+ Distribution.DistributionType distributionType,
+ String toolName,
+ Path outputDirectory,
+ boolean overwrite) {
+ this.logger = logger;
+ this.distributionName = distributionName;
+ this.distributionType = distributionType;
+ this.toolName = toolName;
+ this.outputDirectory = outputDirectory;
+ this.overwrite = overwrite;
+ }
+
+ public String getDistributionName() {
+ return distributionName;
+ }
+
+ public Distribution.DistributionType getDistributionType() {
+ return distributionType;
+ }
+
+ public String getToolName() {
+ return toolName;
+ }
+
+ public Path getOutputDirectory() {
+ return outputDirectory;
+ }
+
+ public boolean isOverwrite() {
+ return overwrite;
+ }
+
+ public boolean generate() throws TemplateGenerationException {
+ if (!Distribution.supportedTools().contains(toolName)) {
+ logger.error("Tool {} is not supported", toolName);
+ return false;
+ }
+
+ Path output = outputDirectory.resolve(distributionName)
+ .resolve(toolName);
+
+ logger.info("Creating output directory {}", output.toAbsolutePath());
+ try {
+ Files.createDirectories(output);
+ } catch (IOException e) {
+ throw fail(e);
+ }
+
+ Map templates = TemplateUtils.resolveTemplates(logger, distributionType, toolName);
+ for (Map.Entry template : templates.entrySet()) {
+ Path outputFile = output.resolve(template.getKey());
+ logger.info("Writing file " + outputFile.toAbsolutePath());
+ try (Writer writer = Files.newBufferedWriter(outputFile, (overwrite ? CREATE : CREATE_NEW), WRITE, TRUNCATE_EXISTING);
+ Scanner scanner = new Scanner(template.getValue())) {
+ while (scanner.hasNextLine()) {
+ writer.write(scanner.nextLine() + System.lineSeparator());
+ }
+ } catch (FileAlreadyExistsException e) {
+ logger.error("File {} already exists and overwrite was set to false.", outputFile.toAbsolutePath());
+ return false;
+ } catch (Exception e) {
+ throw fail(e);
+ }
+ }
+
+ return true;
+ }
+
+ private TemplateGenerationException fail(Exception e) throws TemplateGenerationException {
+ throw new TemplateGenerationException("Unexpected error when generating template. " +
+ "distributionType=" + distributionType +
+ ", distributionName=" + distributionName +
+ ", toolName=" + toolName, e);
+ }
+
+ public static TemplateGeneratorBuilder builder() {
+ return new TemplateGeneratorBuilder();
+ }
+
+ public static class TemplateGeneratorBuilder {
+ private Logger logger;
+ private String distributionName;
+ private Distribution.DistributionType distributionType = Distribution.DistributionType.BINARY;
+ private String toolName;
+ private Path outputDirectory;
+ private boolean overwrite;
+
+ public TemplateGeneratorBuilder logger(Logger logger) {
+ this.logger = requireNonNull(logger, "'logger' must not be null");
+ return this;
+ }
+
+ public TemplateGeneratorBuilder distributionName(String distributionName) {
+ this.distributionName = requireNonBlank(distributionName, "'distributionName' must not be blank");
+ return this;
+ }
+
+ public TemplateGeneratorBuilder distributionType(Distribution.DistributionType distributionType) {
+ this.distributionType = requireNonNull(distributionType, "'distributionType' must not be null");
+ return this;
+ }
+
+ public TemplateGeneratorBuilder toolName(String toolName) {
+ this.toolName = requireNonBlank(toolName, "'toolName' must not be blank");
+ return this;
+ }
+
+ public TemplateGeneratorBuilder outputDirectory(Path outputDirectory) {
+ this.outputDirectory = requireNonNull(outputDirectory, "'outputDirectory' must not be null");
+ return this;
+ }
+
+ public TemplateGeneratorBuilder overwrite(boolean overwrite) {
+ this.overwrite = overwrite;
+ return this;
+ }
+
+ public TemplateGenerator build() {
+ requireNonNull(logger, "'logger' must not be null");
+ requireNonBlank(distributionName, "'distributionName' must not be blank");
+ requireNonNull(distributionType, "'distributionType' must not be null");
+ requireNonBlank(toolName, "'toolName' must not be blank");
+ requireNonNull(outputDirectory, "'outputDirectory' must not be null");
+ return new TemplateGenerator(logger, distributionName, distributionType, toolName, outputDirectory, overwrite);
+ }
+ }
+}
diff --git a/core/jreleaser-templates/src/main/java/org/kordamp/jreleaser/templates/TemplateUtils.java b/core/jreleaser-templates/src/main/java/org/kordamp/jreleaser/templates/TemplateUtils.java
new file mode 100644
index 00000000..ac31e710
--- /dev/null
+++ b/core/jreleaser-templates/src/main/java/org/kordamp/jreleaser/templates/TemplateUtils.java
@@ -0,0 +1,189 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.templates;
+
+import org.kordamp.jreleaser.model.Distribution;
+import org.kordamp.jreleaser.util.Logger;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Enumeration;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public final class TemplateUtils {
+ private TemplateUtils() {
+ // noop
+ }
+
+ public static String trimTplExtension(String str) {
+ if (str.endsWith(".tpl")) {
+ return str.substring(0, str.length() - 4);
+ }
+ return str;
+ }
+
+ public static Map resolveAndMergeTemplates(Logger logger, Distribution.DistributionType distributionType, String toolName, Path templateDirectory) {
+ Map templates = resolveTemplates(logger, distributionType, toolName);
+ if (null != templateDirectory && templateDirectory.toFile().exists()) {
+ templates.putAll(resolveTemplates(logger, distributionType, toolName, templateDirectory));
+ }
+ return templates;
+ }
+
+ public static Map resolveTemplates(Logger logger, Distribution.DistributionType distributionType, String toolName, Path templateDirectory) {
+ Map templates = new LinkedHashMap<>();
+
+ try {
+ Files.walkFileTree(templateDirectory, new SimpleFileVisitor() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ templates.put(templateDirectory.relativize(file).toString(),
+ Files.newBufferedReader(file));
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ } catch (IOException e) {
+ String distributionTypeName = distributionType.name().toLowerCase().replace('_', '-');
+ throw new IllegalStateException("Unexpected error reading templates for distribution " +
+ distributionTypeName + "/" + toolName + " from " + templateDirectory.toAbsolutePath());
+ }
+
+ return templates;
+ }
+
+ public static Map resolveTemplates(Logger logger, Distribution.DistributionType distributionType, String toolName) {
+ String distributionTypeName = distributionType.name().toLowerCase().replace('_', '-');
+ String templatePrefix = "META-INF/jreleaser/templates/" +
+ distributionTypeName + "/" + toolName.toLowerCase();
+
+ Map templates = new LinkedHashMap<>();
+
+ logger.debug("Resolving templates from classpath");
+ URL location = resolveLocation(TemplateUtils.class);
+ if (null == location) {
+ throw new IllegalStateException("Could not find location of classpath templates");
+ }
+
+ try {
+ if ("file".equals(location.getProtocol())) {
+ boolean templateFound = false;
+ JarFile jarFile = new JarFile(new File(location.toURI()));
+ logger.debug("Searching for templates matching {}/*", templatePrefix);
+ for (Enumeration e = jarFile.entries(); e.hasMoreElements(); ) {
+ JarEntry entry = e.nextElement();
+ if (entry.isDirectory() || !entry.getName().startsWith(templatePrefix)) {
+ continue;
+ }
+
+ String templateName = entry.getName().substring(templatePrefix.length() + 1);
+ templates.put(templateName, new InputStreamReader(jarFile.getInputStream(entry)));
+ logger.debug("Found template {}", templateName);
+ templateFound = true;
+ }
+
+ if (!templateFound) {
+ logger.error("Templates for {}/{} were not found", distributionTypeName, toolName);
+ }
+ } else {
+ throw new IllegalStateException("Could not find location of classpath templates");
+ }
+ } catch (URISyntaxException | IOException e) {
+ throw new IllegalStateException("Unexpected error reading templates for distribution " +
+ distributionTypeName + "/" + toolName + " from classpath.");
+ }
+
+ return templates;
+ }
+
+ public static Reader resolveTemplate(Logger logger, Class> anchor, String templateKey) {
+ logger.debug("Resolving template from classpath for {}@{}", anchor.getName(), templateKey);
+ URL location = resolveLocation(anchor);
+ if (null == location) {
+ throw new IllegalStateException("Could not find location of classpath templates");
+ }
+
+ try {
+ if ("file".equals(location.getProtocol())) {
+ JarFile jarFile = new JarFile(new File(location.toURI()));
+ logger.debug("Searching for template matching {}", templateKey);
+ for (Enumeration e = jarFile.entries(); e.hasMoreElements(); ) {
+ JarEntry entry = e.nextElement();
+ if (entry.isDirectory() || !entry.getName().equals(templateKey)) {
+ continue;
+ }
+
+ logger.debug("Found template {}", templateKey);
+ return new InputStreamReader(jarFile.getInputStream(entry));
+ }
+ throw new IllegalStateException("Template for " +
+ anchor.getName() + "@" + templateKey + " were not found");
+ } else {
+ throw new IllegalStateException("Could not find location of classpath templates");
+ }
+ } catch (URISyntaxException | IOException e) {
+ throw new IllegalStateException("Unexpected error reading template for " +
+ anchor.getName() + "@" + templateKey + " from classpath.");
+ }
+ }
+
+ private static URL resolveLocation(Class> klass) {
+ if (klass == null) return null;
+
+ try {
+ URL codeSourceLocation = klass.getProtectionDomain()
+ .getCodeSource()
+ .getLocation();
+ if (codeSourceLocation != null) return codeSourceLocation;
+ } catch (SecurityException | NullPointerException ignored) {
+ // noop
+ }
+
+ URL classResource = klass.getResource(klass.getSimpleName() + ".class");
+ if (classResource == null) return null;
+
+ String url = classResource.toString();
+ String suffix = klass.getCanonicalName().replace('.', '/') + ".class";
+ if (!url.endsWith(suffix)) return null;
+ String path = url.substring(0, url.length() - suffix.length());
+
+ if (path.startsWith("jar:")) path = path.substring(4, path.length() - 2);
+
+ try {
+ return new URL(path);
+ } catch (MalformedURLException ignored) {
+ return null;
+ }
+ }
+}
diff --git a/core/jreleaser-templates/src/main/module/module-info.java b/core/jreleaser-templates/src/main/module/module-info.java
new file mode 100644
index 00000000..10a48fd4
--- /dev/null
+++ b/core/jreleaser-templates/src/main/module/module-info.java
@@ -0,0 +1,24 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+module org.kordamp.jreleaser.templates {
+ exports org.kordamp.jreleaser.templates;
+ requires org.kordamp.jreleaser.model;
+ requires org.kordamp.jreleaser.util;
+ requires com.github.mustachejava;
+}
\ No newline at end of file
diff --git a/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/binary/brew/formula.rb.tpl b/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/binary/brew/formula.rb.tpl
new file mode 100644
index 00000000..cd18454a
--- /dev/null
+++ b/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/binary/brew/formula.rb.tpl
@@ -0,0 +1,23 @@
+class {{projectNameCapitalized}} < Formula
+ desc "{{projectDescription}}"
+ homepage "{{projectWebsite}}"
+ url "{{distributionUrl}}"
+ sha256 "{{distributionSha256}}"
+ license "{{projectLicense}}"
+
+ bottle :unneeded
+
+ {{#brewDependencies}}
+ depends_on {{formattedDependency}}
+ {{/brewDependencies}}
+
+ def install
+ libexec.install Dir["*"]
+ bin.install_symlink "#{libexec}/bin/{{distributionExecutable}}"
+ end
+
+ test do
+ output = shell_output("#{bin}/{{distributionExecutable}} --version")
+ assert_match "{{projectVersion}}", output
+ end
+end
diff --git a/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/binary/chocolatey/binary.nuspec b/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/binary/chocolatey/binary.nuspec
new file mode 100644
index 00000000..5a909c9e
--- /dev/null
+++ b/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/binary/chocolatey/binary.nuspec
@@ -0,0 +1,22 @@
+
+
+
+
+
+ {{distributionName}}
+ {{projectVersion}}
+ {{projectAuthorsByComma}}
+ {{projectLongDescription}}
+
+ {{projectName}}
+ {{projectWebsite}}
+ {{projectLicense}}
+ false
+ {{distributionTagsBySpace}}
+ {{projectDescription}}
+ {{distributionReleaseNotes}}
+
+
+
+
+
diff --git a/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/binary/chocolatey/tools/chocolateyinstall.ps1 b/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/binary/chocolatey/tools/chocolateyinstall.ps1
new file mode 100644
index 00000000..fb1a69c9
--- /dev/null
+++ b/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/binary/chocolatey/tools/chocolateyinstall.ps1
@@ -0,0 +1,13 @@
+$tools = Split-Path $MyInvocation.MyCommand.Definition
+$package = Split-Path $tools
+$app_home = Join-Path $package '{{projectName}}-{{projectVersion}}'
+$app_bat = Join-Path $app_home 'bin/{{distributionExecutable}}.cmd'
+
+Install-ChocolateyZipPackage `
+ -PackageName '{{distributionName}}' `
+ -Url '{{distributionUrl}}' `
+ -Checksum '{{distributionSha256}}' `
+ -ChecksumType 'sha256' `
+ -UnzipLocation $package
+
+Install-BinFile -Name '{{distributionExecutable}}' -Path $app_bat
\ No newline at end of file
diff --git a/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/binary/chocolatey/tools/chocolateyuninstall.ps1 b/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/binary/chocolatey/tools/chocolateyuninstall.ps1
new file mode 100644
index 00000000..67d06958
--- /dev/null
+++ b/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/binary/chocolatey/tools/chocolateyuninstall.ps1
@@ -0,0 +1,6 @@
+$tools = Split-Path $MyInvocation.MyCommand.Definition
+$package = Split-Path $tools
+$app_home = Join-Path $package '{{projectName}}-{{projectVersion}}'
+$app_bat = Join-Path $app_home 'bin/{{distributionExecutable}}.cmd'
+
+Uninstall-BinFile -Name '{{distributionExecutable}}' -Path $app_bat
diff --git a/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/binary/scoop/manifest.json.tpl b/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/binary/scoop/manifest.json.tpl
new file mode 100644
index 00000000..8d5c2941
--- /dev/null
+++ b/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/binary/scoop/manifest.json.tpl
@@ -0,0 +1,27 @@
+{
+ "homepage": "{{projectWebsite}}",
+ "description": "{{projectDescription}}",
+ "version": "{{projectVersion}}",
+ "license": "{{projectLicense}}",
+ "url": "{{distributionUrl}}",
+ "hash": "sha256:{{distributionSha256}}",
+ "extract_dir": "{{projectName}}-{{projectVersion}}",
+ "env_add_path": "bin",
+ "suggest": {
+ "JDK": [
+ "java/oraclejdk",
+ "java/openjdk"
+ ]
+ },
+ "checkver": {
+ "url": "{{scoopCheckverUrl}}",
+ "re": "v([\\d.]+).zip"
+ },
+ "autoupdate": {
+ "url": "{{scoopAutoupdateUrl}}",
+ "extract_dir": "{{projectName}}-$version",
+ "hash": {
+ "url": "$url.sha256"
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/binary/snap/snap/snapcraft.yaml.tpl b/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/binary/snap/snap/snapcraft.yaml.tpl
new file mode 100644
index 00000000..7eb77b23
--- /dev/null
+++ b/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/binary/snap/snap/snapcraft.yaml.tpl
@@ -0,0 +1,61 @@
+name: {{distributionName}}
+base: {{snapBase}}
+version: {{projectVersion}}
+license: {{projectLicense}}
+grade: {{snapGrade}}
+type: app
+confinement: {{snapConfinement}}
+summary: {{projectDescription}}
+description: {{projectLongDescription}}
+
+apps:
+ {{distributionExecutable}}:
+ command: bin/{{distributionExecutable}}
+ environment:
+ JAVA_HOME: $SNAP/usr/lib/jvm/java
+
+{{#snapHasPlugs}}
+plugs:
+ {{#snapPlugs}}
+ {{name}}:
+ {{#attributes}}
+ {{key}}: {{value}}
+ {{/attributes}}
+ {{/snapPlugs}}
+{{/snapHasPlugs}}
+{{#snapHasSlots}}
+slots:
+ {{#snapSlots}}
+ {{name}}:
+ {{#attributes}}
+ {{key}}: {{value}}
+ {{/attributes}}
+ {{#hasReads}}
+ reads:
+ {{#reads}}
+ - {{.}}
+ {{/reads}}
+ {{/hasReads}}
+ {{#hasWrites}}
+ writes:
+ {{#writes}}
+ - {{.}}
+ {{/writes}}
+ {{/hasWrites}}
+ {{/snapSlots }}
+{{/snapHasSlots}}
+parts:
+ {{distributionExecutable}}:
+ plugin: dump
+ source: {{distributionUrl}}
+ source-checksum: sha256/{{distributionSha256}}
+ stage-packages:
+ - openjdk-{{javaVersion}}-jdk
+ organize:
+ usr/lib/jvm/java-{{javaVersion}}-openjdk*: usr/lib/jvm/java
+ {{#snapHasLocalPlugs}}
+ plugs:
+ {{#snapLocalPlugs}}
+ - {{.}}
+ {{/snapLocalPlugs}}
+ {{/snapHasLocalPlugs}}
\ No newline at end of file
diff --git a/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/scripts/executable.bat.tpl b/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/scripts/executable.bat.tpl
new file mode 100644
index 00000000..19176db9
--- /dev/null
+++ b/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/scripts/executable.bat.tpl
@@ -0,0 +1,103 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem {{distributionExecutable}} startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%..
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-jar {{artifactFileName}}"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=
+
+@rem Execute {{distributionExecutable}}
+"%JAVA_EXE%" %JAVA_OPTS% %DEFAULT_JVM_OPTS% %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable APP_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%APP_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/scripts/executable.tpl b/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/scripts/executable.tpl
new file mode 100755
index 00000000..44071fd0
--- /dev/null
+++ b/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/scripts/executable.tpl
@@ -0,0 +1,183 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## {{distributionExecutable}} start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/.." >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="{{distributionExecutable}}"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-jar {{artifactFileName}}"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $JAVA_OPTS $DEFAULT_JVM_OPTS "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/single-jar/snap/snap/snapcraft.yaml.tpl b/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/single-jar/snap/snap/snapcraft.yaml.tpl
new file mode 100644
index 00000000..a48b4a96
--- /dev/null
+++ b/core/jreleaser-templates/src/main/resources/META-INF/jreleaser/templates/single-jar/snap/snap/snapcraft.yaml.tpl
@@ -0,0 +1,62 @@
+name: {{distributionName}}
+base: {{snapBase}}
+version: {{projectVersion}}
+license: {{projectLicense}}
+grade: {{snapGrade}}
+type: app
+confinement: {{snapConfinement}}
+summary: {{projectDescription}}
+description: {{projectLongDescription}}
+
+apps:
+ {{distributionExecutable}}:
+ command: ${JAVA_HOME}/bin/java -jar $SNAP/{{distributionFileName}}
+ environment:
+ JAVA_HOME: $SNAP/usr/lib/jvm/java
+
+{{#snapHasPlugs}}
+plugs:
+ {{#snapPlugs}}
+ {{name}}:
+ {{#attributes}}
+ {{key}}: {{value}}
+ {{/attributes}}
+ {{/snapPlugs}}
+{{/snapHasPlugs}}
+{{#snapHasSlots}}
+slots:
+ {{#snapSlots}}
+ {{name}}:
+ {{#attributes}}
+ {{key}}: {{value}}
+ {{/attributes}}
+ {{#hasReads}}
+ reads:
+ {{#reads}}
+ - {{.}}
+ {{/reads}}
+ {{/hasReads}}
+ {{#hasWrites}}
+ writes:
+ {{#writes}}
+ - {{.}}
+ {{/writes}}
+ {{/hasWrites}}
+ {{/snapSlots }}
+{{/snapHasSlots}}
+
+parts:
+ {{distributionExecutable}}:
+ plugin: nil
+ source: {{distributionUrl}}
+ source-checksum: sha256/{{distributionSha256}}
+ stage-packages:
+ - openjdk-{{javaVersion}}-jdk
+ organize:
+ usr/lib/jvm/java-{{javaVersion}}-openjdk*: usr/lib/jvm/java
+ {{#snapHasLocalPlugs}}
+ plugs:
+ {{#snapLocalPlugs}}
+ - {{.}}
+ {{/snapLocalPlugs}}
+ {{/snapHasLocalPlugs}}
\ No newline at end of file
diff --git a/core/jreleaser-tools/gradle.properties b/core/jreleaser-tools/gradle.properties
new file mode 100644
index 00000000..6449a233
--- /dev/null
+++ b/core/jreleaser-tools/gradle.properties
@@ -0,0 +1,19 @@
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Copyright 2020 Andres Almiray.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+project_description = JReleaser Execution
\ No newline at end of file
diff --git a/core/jreleaser-tools/jreleaser-tools.gradle b/core/jreleaser-tools/jreleaser-tools.gradle
new file mode 100644
index 00000000..80d4ac1b
--- /dev/null
+++ b/core/jreleaser-tools/jreleaser-tools.gradle
@@ -0,0 +1,24 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+dependencies {
+ api project(':jreleaser-model')
+ api project(':jreleaser-templates')
+ api "com.github.spullara.mustache.java:compiler:$mustacheVersion"
+ api "org.zeroturnaround:zt-exec:$ztexecVersion"
+}
\ No newline at end of file
diff --git a/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/AbstractToolProcessor.java b/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/AbstractToolProcessor.java
new file mode 100644
index 00000000..4646d03c
--- /dev/null
+++ b/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/AbstractToolProcessor.java
@@ -0,0 +1,281 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.tools;
+
+import org.kordamp.jreleaser.model.Artifact;
+import org.kordamp.jreleaser.model.Distribution;
+import org.kordamp.jreleaser.model.JReleaserModel;
+import org.kordamp.jreleaser.model.Project;
+import org.kordamp.jreleaser.model.Release;
+import org.kordamp.jreleaser.model.Tool;
+import org.kordamp.jreleaser.util.Logger;
+import org.zeroturnaround.exec.ProcessExecutor;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+import static java.nio.file.StandardOpenOption.CREATE;
+import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
+import static java.nio.file.StandardOpenOption.WRITE;
+import static org.kordamp.jreleaser.templates.TemplateUtils.resolveAndMergeTemplates;
+import static org.kordamp.jreleaser.tools.ProcessorUtils.applyTemplate;
+import static org.kordamp.jreleaser.util.FileUtils.createDirectoriesWithFullAccess;
+import static org.kordamp.jreleaser.util.FileUtils.grantFullAccess;
+import static org.kordamp.jreleaser.util.StringUtils.capitalize;
+import static org.kordamp.jreleaser.util.StringUtils.getClassNameForLowerCaseHyphenSeparatedName;
+import static org.kordamp.jreleaser.util.StringUtils.isBlank;
+import static org.kordamp.jreleaser.util.StringUtils.isNotBlank;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+abstract class AbstractToolProcessor implements ToolProcessor {
+ private final Logger logger;
+ private final JReleaserModel model;
+ private final T tool;
+
+ protected AbstractToolProcessor(Logger logger, JReleaserModel model, T tool) {
+ this.logger = logger;
+ this.model = model;
+ this.tool = tool;
+ }
+
+ @Override
+ public T getTool() {
+ return tool;
+ }
+
+ @Override
+ public String getToolName() {
+ return tool.getToolName();
+ }
+
+ @Override
+ public Logger getLogger() {
+ return logger;
+ }
+
+ @Override
+ public JReleaserModel getModel() {
+ return model;
+ }
+
+ @Override
+ public boolean prepareDistribution(Distribution distribution, Map context) throws ToolProcessingException {
+ Tool tool = distribution.getTool(getToolName());
+
+ try {
+ String distributionName = distribution.getName();
+ getLogger().debug("Creating context for {}/{}", distributionName, getToolName());
+ Map newContext = fillContext(distribution, context);
+ if (newContext.isEmpty()) {
+ logger.warn("Skipping {} tool for {} distribution", getToolName(), distributionName);
+ return false;
+ }
+ getLogger().debug("Resolving templates for {}/{}", distributionName, getToolName());
+ Map templates = resolveAndMergeTemplates(logger, distribution.getType(), getToolName(), tool.getTemplateDirectory());
+ for (Map.Entry entry : templates.entrySet()) {
+ getLogger().debug("Evaluating template {} for {}/{}", entry.getKey(), distributionName, getToolName());
+ String content = applyTemplate(entry.getValue(), newContext);
+ getLogger().debug("Writing template {} for {}/{}", entry.getKey(), distributionName, getToolName());
+ writeFile(model.getProject(), distribution, content, context, entry.getKey());
+ }
+ } catch (IllegalArgumentException e) {
+ throw new ToolProcessingException(e);
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean packageDistribution(Distribution distribution, Map context) throws ToolProcessingException {
+ try {
+ String distributionName = distribution.getName();
+ getLogger().debug("Creating context for {}/{}", distributionName, getToolName());
+ Map newContext = fillContext(distribution, context);
+ if (newContext.isEmpty()) {
+ logger.warn("Skipping {} tool for {} distribution", getToolName(), distributionName);
+ return false;
+ }
+ return doPackageDistribution(distribution, newContext);
+ } catch (IllegalArgumentException e) {
+ throw new ToolProcessingException(e);
+ }
+ }
+
+ protected abstract boolean doPackageDistribution(Distribution distribution, Map context) throws ToolProcessingException;
+
+ protected abstract void writeFile(Project project, Distribution distribution, String content, Map context, String fileName) throws ToolProcessingException;
+
+ protected void writeFile(String content, Path outputFile) throws ToolProcessingException {
+ try {
+ createDirectoriesWithFullAccess(outputFile.getParent());
+ Files.write(outputFile, content.getBytes(), CREATE, WRITE, TRUNCATE_EXISTING);
+ grantFullAccess(outputFile);
+ } catch (IOException e) {
+ throw new ToolProcessingException("Unexpected error when writing to " + outputFile.toAbsolutePath(), e);
+ }
+ }
+
+ protected Map fillContext(Distribution distribution, Map context) throws ToolProcessingException {
+ Map newContext = new LinkedHashMap<>(context);
+ getLogger().debug("Filling project properties into context");
+ fillProjectProperties(newContext, model.getProject());
+ getLogger().debug("Filling release properties into context");
+ fillReleaseProperties(newContext, model.getRelease());
+ getLogger().debug("Filling distribution properties into context");
+ fillDistributionProperties(newContext, distribution, model.getRelease());
+ getLogger().debug("Filling artifact properties into context");
+ if (!verifyAndAddArtifacts(newContext, distribution)) {
+ // we can't continue with this tool
+ return Collections.emptyMap();
+ }
+ getLogger().debug("Filling tool properties into context");
+ fillToolProperties(newContext, distribution);
+ newContext.putAll(tool.getExtraProperties());
+ return newContext;
+ }
+
+ protected void fillProjectProperties(Map context, Project project) {
+ context.put(Constants.KEY_PROJECT_NAME, project.getName());
+ context.put(Constants.KEY_PROJECT_NAME_CAPITALIZED, getClassNameForLowerCaseHyphenSeparatedName(project.getName()));
+ context.put(Constants.KEY_PROJECT_VERSION, project.getVersion());
+ context.put(Constants.KEY_PROJECT_DESCRIPTION, project.getDescription());
+ context.put(Constants.KEY_PROJECT_LONG_DESCRIPTION, project.getLongDescription());
+ context.put(Constants.KEY_PROJECT_WEBSITE, project.getWebsite());
+ context.put(Constants.KEY_PROJECT_LICENSE, project.getLicense());
+ context.put(Constants.KEY_JAVA_VERSION, project.getJavaVersion());
+ context.put(Constants.KEY_PROJECT_AUTHORS_BY_SPACE, String.join(" ", project.getAuthors()));
+ context.put(Constants.KEY_PROJECT_AUTHORS_BY_COMMA, String.join(",", project.getAuthors()));
+ context.put(Constants.KEY_PROJECT_TAGS_BY_SPACE, String.join(" ", project.getTags()));
+ context.put(Constants.KEY_PROJECT_TAGS_BY_COMMA, String.join(",", project.getTags()));
+ context.putAll(project.getExtraProperties());
+ }
+
+ protected void fillReleaseProperties(Map context, Release release) {
+ context.put(Constants.KEY_REPO_HOST, release.getRepoHost());
+ context.put(Constants.KEY_REPO_OWNER, release.getRepoOwner());
+ context.put(Constants.KEY_REPO_NAME, release.getRepoName());
+ }
+
+ protected void fillDistributionProperties(Map context, Distribution distribution, Release release) {
+ context.put(Constants.KEY_DISTRIBUTION_NAME, distribution.getName());
+ context.put(Constants.KEY_DISTRIBUTION_EXECUTABLE, distribution.getExecutable());
+ context.put(Constants.KEY_DISTRIBUTION_TAGS_BY_SPACE, String.join(" ", distribution.getTags()));
+ context.put(Constants.KEY_DISTRIBUTION_TAGS_BY_COMMA, String.join(",", distribution.getTags()));
+ context.put(Constants.KEY_DISTRIBUTION_RELEASE_NOTES, applyTemplate(new StringReader(release.getReleaseNotesUrlFormat()), context));
+ context.put(Constants.KEY_DISTRIBUTION_ISSUE_TRACKER, applyTemplate(new StringReader(release.getIssueTrackerUrlFormat()), context));
+ context.put(Constants.KEY_DISTRIBUTION_LATEST_RELEASE, applyTemplate(new StringReader(release.getLatestReleaseUrlFormat()), context));
+ context.putAll(distribution.getExtraProperties());
+ }
+
+ protected abstract void fillToolProperties(Map context, Distribution distribution) throws ToolProcessingException;
+
+ protected boolean executeCommand(List cmd) throws ToolProcessingException {
+ try {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ByteArrayOutputStream err = new ByteArrayOutputStream();
+
+ int exitValue = new ProcessExecutor(cmd)
+ .redirectOutput(out)
+ .redirectError(err)
+ .execute()
+ .getExitValue();
+
+ info(out);
+ error(err);
+
+ if (exitValue == 0) return true;
+ throw new ToolProcessingException("Command execution error. exitValue = " + exitValue);
+ } catch (Exception e) {
+ if (e instanceof ToolProcessingException) {
+ throw (ToolProcessingException) e;
+ }
+ throw new ToolProcessingException("Unexpected error", e);
+ }
+ }
+
+ private boolean verifyAndAddArtifacts(Map context,
+ Distribution distribution) throws ToolProcessingException {
+ Set fileExtensions = resolveByExtensionsFor(distribution.getType());
+ List artifacts = distribution.getArtifacts().stream()
+ .filter(artifact -> fileExtensions.stream().anyMatch(ext -> artifact.getPath().endsWith(ext)))
+ .collect(Collectors.toList());
+
+ if (artifacts.size() == 0) {
+ // we can't proceed
+ logger.warn("No suitable artifacts found in distribution {} to be packaged with {}",
+ distribution.getName(), capitalize(tool.getToolName()));
+ return false;
+ }
+
+ for (int i = 0; i < artifacts.size(); i++) {
+ Artifact artifact = artifacts.get(i);
+ String classifier = isNotBlank(artifact.getOsClassifier()) ? capitalize(artifact.getOsClassifier()) : "";
+ String artifactFileName = Paths.get(artifact.getPath()).getFileName().toString();
+ context.put("artifact" + classifier + "JavaVersion", artifact.getJavaVersion());
+ context.put("artifact" + classifier + "FileName", artifactFileName);
+ context.put("artifact" + classifier + "Hash", artifact.getHash());
+ Map newContext = new LinkedHashMap<>(context);
+ newContext.put("artifactFileName", artifactFileName);
+ String artifactUrl = applyTemplate(new StringReader(model.getRelease().getDownloadUrlFormat()), newContext, "downloadUrl");
+ context.put("artifact" + classifier + "Url", artifactUrl);
+
+ if (0 == i) {
+ context.put(Constants.KEY_DISTRIBUTION_URL, artifactUrl);
+ context.put(Constants.KEY_DISTRIBUTION_SHA_256, artifact.getHash());
+ context.put(Constants.KEY_DISTRIBUTION_JAVA_VERSION, artifact.getJavaVersion());
+ context.put(Constants.KEY_DISTRIBUTION_FILE_NAME, artifactFileName);
+ }
+ }
+
+ return true;
+ }
+
+ protected abstract Set resolveByExtensionsFor(Distribution.DistributionType type);
+
+ protected void info(ByteArrayOutputStream out) {
+ log(out, logger::info);
+ }
+
+ protected void error(ByteArrayOutputStream err) {
+ log(err, logger::error);
+ }
+
+ private void log(ByteArrayOutputStream stream, Consumer super String> consumer) {
+ String str = stream.toString();
+ if (isBlank(str)) return;
+
+ Arrays.stream(str.split(System.lineSeparator()))
+ .forEach(consumer);
+ }
+}
diff --git a/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/BrewToolProcessor.java b/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/BrewToolProcessor.java
new file mode 100644
index 00000000..150f3c64
--- /dev/null
+++ b/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/BrewToolProcessor.java
@@ -0,0 +1,108 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.tools;
+
+import org.kordamp.jreleaser.model.Brew;
+import org.kordamp.jreleaser.model.Distribution;
+import org.kordamp.jreleaser.model.JReleaserModel;
+import org.kordamp.jreleaser.model.Project;
+import org.kordamp.jreleaser.util.Logger;
+
+import java.nio.file.Path;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.kordamp.jreleaser.templates.TemplateUtils.trimTplExtension;
+import static org.kordamp.jreleaser.util.StringUtils.isNotBlank;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class BrewToolProcessor extends AbstractToolProcessor {
+ public BrewToolProcessor(Logger logger, JReleaserModel model, Brew brew) {
+ super(logger, model, brew);
+ }
+
+ @Override
+ protected boolean doPackageDistribution(Distribution distribution, Map context) throws ToolProcessingException {
+ getLogger().debug("Tool {} does not require additional packaging", getToolName());
+ return true;
+ }
+
+ @Override
+ protected Set resolveByExtensionsFor(Distribution.DistributionType type) {
+ Set set = new LinkedHashSet<>();
+ set.add(".zip");
+ return set;
+ }
+
+ @Override
+ protected void fillToolProperties(Map context, Distribution distribution) throws ToolProcessingException {
+ if (!getTool().getDependencies().containsKey(Constants.KEY_JAVA_VERSION)) {
+ getTool().getDependencies().put(":java", (String) context.get(Constants.KEY_DISTRIBUTION_JAVA_VERSION));
+ }
+
+ context.put(Constants.KEY_BREW_DEPENDENCIES, getTool().getDependencies()
+ .entrySet().stream()
+ .map(entry -> new Dependency(entry.getKey(), entry.getValue()))
+ .collect(Collectors.toList()));
+ }
+
+ @Override
+ protected void writeFile(Project project, Distribution distribution, String content, Map context, String fileName)
+ throws ToolProcessingException {
+ fileName = trimTplExtension(fileName);
+
+ Path outputDirectory = (Path) context.get(Constants.KEY_PREPARE_DIRECTORY);
+ Path outputFile = "formula.rb".equals(fileName) ?
+ outputDirectory.resolve("Formula").resolve(distribution.getExecutable().concat(".rb")) :
+ outputDirectory.resolve(fileName);
+
+ writeFile(content, outputFile);
+ }
+
+ public static class Dependency {
+ private final String key;
+ private final String value;
+
+ public Dependency(String key, String value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ public String getFormattedDependency() {
+ StringBuilder formatted = new StringBuilder();
+ if (key.startsWith(":")) {
+ formatted.append(key);
+ } else {
+ formatted.append("\"")
+ .append(key)
+ .append("\"");
+ }
+ if (isNotBlank(value)) {
+ formatted.append(" => \"")
+ .append(value)
+ .append("\"");
+ }
+ return "!!" + formatted.toString() + "!!";
+ }
+ }
+}
diff --git a/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/ChocolateyToolProcessor.java b/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/ChocolateyToolProcessor.java
new file mode 100644
index 00000000..d276d28b
--- /dev/null
+++ b/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/ChocolateyToolProcessor.java
@@ -0,0 +1,77 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.tools;
+
+import org.kordamp.jreleaser.model.Chocolatey;
+import org.kordamp.jreleaser.model.Distribution;
+import org.kordamp.jreleaser.model.JReleaserModel;
+import org.kordamp.jreleaser.model.Project;
+import org.kordamp.jreleaser.util.Logger;
+import org.kordamp.jreleaser.util.OsUtils;
+
+import java.nio.file.Path;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static org.kordamp.jreleaser.templates.TemplateUtils.trimTplExtension;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class ChocolateyToolProcessor extends AbstractToolProcessor {
+ public ChocolateyToolProcessor(Logger logger, JReleaserModel model, Chocolatey tool) {
+ super(logger, model, tool);
+ }
+
+ @Override
+ protected boolean doPackageDistribution(Distribution distribution, Map context) throws ToolProcessingException {
+ if (!OsUtils.isWindows()) {
+ getLogger().debug("Tool {} must run on Windows", getToolName());
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ protected Set resolveByExtensionsFor(Distribution.DistributionType type) {
+ Set set = new LinkedHashSet<>();
+ set.add(".zip");
+ return set;
+ }
+
+ @Override
+ protected void fillToolProperties(Map context, Distribution distribution) throws ToolProcessingException {
+ // noop
+ }
+
+ @Override
+ protected void writeFile(Project project, Distribution distribution, String content, Map context, String fileName)
+ throws ToolProcessingException {
+ fileName = trimTplExtension(fileName);
+
+ Path outputDirectory = (Path) context.get(Constants.KEY_PREPARE_DIRECTORY);
+ Path outputFile = "binary.nuspec".equals(fileName) ?
+ outputDirectory.resolve(distribution.getExecutable().concat(".nuspec")) :
+ outputDirectory.resolve(fileName);
+
+ writeFile(content, outputFile);
+ }
+}
diff --git a/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/Constants.java b/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/Constants.java
new file mode 100644
index 00000000..ccc7d351
--- /dev/null
+++ b/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/Constants.java
@@ -0,0 +1,85 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.tools;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public interface Constants {
+ // General
+ String KEY_JAVA_VERSION = "javaVersion";
+
+ // Project
+ String KEY_PROJECT_NAME = "projectName";
+ String KEY_PROJECT_NAME_CAPITALIZED = "projectNameCapitalized";
+ String KEY_PROJECT_VERSION = "projectVersion";
+ String KEY_PROJECT_DESCRIPTION = "projectDescription";
+ String KEY_PROJECT_LONG_DESCRIPTION = "projectLongDescription";
+ String KEY_PROJECT_WEBSITE = "projectWebsite";
+ String KEY_PROJECT_LICENSE = "projectLicense";
+ String KEY_PROJECT_AUTHORS_BY_SPACE = "projectAuthorsBySpace";
+ String KEY_PROJECT_AUTHORS_BY_COMMA = "projectAuthorsByComma";
+ String KEY_PROJECT_TAGS_BY_SPACE = "projectTagsBySpace";
+ String KEY_PROJECT_TAGS_BY_COMMA = "projectTagsByComma";
+
+ // Release
+ String KEY_REPO_HOST = "repoHost";
+ String KEY_REPO_OWNER = "repoOwner";
+ String KEY_REPO_NAME = "repoName";
+
+ // Distribution
+ String KEY_DISTRIBUTION_NAME = "distributionName";
+ String KEY_DISTRIBUTION_EXECUTABLE = "distributionExecutable";
+ String KEY_DISTRIBUTION_TAGS_BY_SPACE = "distributionTagsBySpace";
+ String KEY_DISTRIBUTION_TAGS_BY_COMMA = "distributionTagsByComma";
+ String KEY_DISTRIBUTION_RELEASE_NOTES = "distributionReleaseNotes";
+ String KEY_DISTRIBUTION_ISSUE_TRACKER = "distributionIssueTracker";
+ String KEY_DISTRIBUTION_LATEST_RELEASE = "distributionLatestRelease";
+ String KEY_DISTRIBUTION_JAVA_VERSION = "distributionJavaVersion";
+ String KEY_DISTRIBUTION_URL = "distributionUrl";
+ String KEY_DISTRIBUTION_SHA_256 = "distributionSha256";
+ String KEY_DISTRIBUTION_FILE_NAME = "distributionFileName";
+
+ // Artifact
+ String KEY_ARTIFACT_FILE_NAME = "artifactFileName";
+
+ // Brew
+ String KEY_BREW_DEPENDENCIES = "brewDependencies";
+
+ // Scoop
+ String KEY_SCOOP_CHECKVER_URL = "scoopCheckverUrl";
+ String KEY_SCOOP_AUTOUPDATE_URL = "scoopAutoupdateUrl";
+
+ // Snap
+ String KEY_SNAP_BASE = "snapBase";
+ String KEY_SNAP_GRADE = "snapGrade";
+ String KEY_SNAP_CONFINEMENT = "snapConfinement";
+ String KEY_SNAP_HAS_PLUGS = "snapHasPlugs";
+ String KEY_SNAP_PLUGS = "snapPlugs";
+ String KEY_SNAP_HAS_SLOTS = "snapHasSlots";
+ String KEY_SNAP_SLOTS = "snapSlots";
+ String KEY_SNAP_HAS_LOCAL_PLUGS = "snapHasLocalPlugs";
+ String KEY_SNAP_LOCAL_PLUGS = "snapLocalPlugs";
+
+ // ToolProcessor
+ String KEY_CHECKSUM_DIRECTORY = "__CHECKSUM_DIRECTORY__";
+ String KEY_OUTPUT_DIRECTORY = "__OUTPUT_DIRECTORY__";
+ String KEY_PREPARE_DIRECTORY = "__PREPARE_DIRECTORY__";
+ String KEY_PACKAGE_DIRECTORY = "__PACKAGE_DIRECTORY__";
+}
diff --git a/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/DistributionProcessor.java b/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/DistributionProcessor.java
new file mode 100644
index 00000000..30165787
--- /dev/null
+++ b/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/DistributionProcessor.java
@@ -0,0 +1,198 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.tools;
+
+import org.kordamp.jreleaser.model.Artifact;
+import org.kordamp.jreleaser.model.Distribution;
+import org.kordamp.jreleaser.model.JReleaserModel;
+import org.kordamp.jreleaser.model.Tool;
+import org.kordamp.jreleaser.util.Logger;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static java.util.Objects.requireNonNull;
+import static org.kordamp.jreleaser.util.StringUtils.requireNonBlank;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class DistributionProcessor {
+ private final Logger logger;
+ private final JReleaserModel model;
+ private final String distributionName;
+ private final String toolName;
+ private final Path checksumDirectory;
+ private final Path outputDirectory;
+
+ private DistributionProcessor(Logger logger,
+ JReleaserModel model,
+ String distributionName,
+ String toolName,
+ Path checksumDirectory,
+ Path outputDirectory) {
+ this.logger = logger;
+ this.model = model;
+ this.distributionName = distributionName;
+ this.toolName = toolName;
+ this.checksumDirectory = checksumDirectory;
+ this.outputDirectory = outputDirectory;
+ }
+
+ public JReleaserModel getModel() {
+ return model;
+ }
+
+ public String getDistributionName() {
+ return distributionName;
+ }
+
+ public String getToolName() {
+ return toolName;
+ }
+
+ public Path getChecksumDirectory() {
+ return checksumDirectory;
+ }
+
+ public Path getOutputDirectory() {
+ return outputDirectory;
+ }
+
+ public boolean prepareDistribution() throws ToolProcessingException {
+ Distribution distribution = model.findDistribution(distributionName);
+ Tool tool = distribution.getTool(toolName);
+ if (!tool.isEnabled()) {
+ logger.warn("Skipping {} tool for {} distribution", toolName, distributionName);
+ return false;
+ }
+
+ logger.info("Preparing {} distribution with tool {}", distributionName, toolName);
+
+ Map context = new LinkedHashMap<>();
+ context.put(Constants.KEY_CHECKSUM_DIRECTORY, checksumDirectory);
+ context.put(Constants.KEY_OUTPUT_DIRECTORY, outputDirectory);
+ context.put(Constants.KEY_PREPARE_DIRECTORY, outputDirectory.resolve("prepare"));
+
+ logger.debug("Reading checksums for {} distribution", distributionName);
+ for (int i = 0; i < distribution.getArtifacts().size(); i++) {
+ Artifact artifact = distribution.getArtifacts().get(i);
+ readHash(artifact, checksumDirectory);
+ }
+
+ ToolProcessor> toolProcessor = ToolProcessors.findProcessor(logger, model, tool);
+ return toolProcessor.prepareDistribution(distribution, context);
+ }
+
+ public boolean packageDistribution() throws ToolProcessingException {
+ Distribution distribution = model.findDistribution(distributionName);
+ Tool tool = distribution.getTool(toolName);
+ if (!tool.isEnabled()) {
+ logger.warn("Skipping {} tool for {} distribution", toolName, distributionName);
+ return false;
+ }
+
+ logger.info("Packaging {} distribution with tool {}", distributionName, toolName);
+
+ Map context = new LinkedHashMap<>();
+ context.put(Constants.KEY_OUTPUT_DIRECTORY, outputDirectory);
+ context.put(Constants.KEY_PREPARE_DIRECTORY, outputDirectory.resolve("prepare"));
+ context.put(Constants.KEY_PACKAGE_DIRECTORY, outputDirectory.resolve("package"));
+
+ ToolProcessor> toolProcessor = ToolProcessors.findProcessor(logger, model, tool);
+ return toolProcessor.packageDistribution(distribution, context);
+ }
+
+ private void readHash(Artifact artifact, Path checksumDirectory) throws ToolProcessingException {
+ Path artifactPath = Paths.get(artifact.getPath());
+ Path checksumPath = checksumDirectory.resolve(artifactPath.getFileName() + ".sha256");
+
+ if (!artifactPath.toFile().exists()) {
+ throw new ToolProcessingException("Artifact does not exist. " + artifactPath.toAbsolutePath());
+ }
+
+ if (!checksumPath.toFile().exists()) {
+ throw new ToolProcessingException("Artifact checksum does not exist. " + checksumPath.toAbsolutePath());
+ }
+
+ try {
+ logger.debug("Reading checksum for {} from {}", artifactPath.toAbsolutePath(), checksumPath.toAbsolutePath());
+ artifact.setHash(new String(Files.readAllBytes(checksumPath)));
+ } catch (IOException e) {
+ throw new ToolProcessingException("Unexpected error when reading hash from " + checksumPath.toAbsolutePath(), e);
+ }
+ }
+
+ public static DistributionProcessorBuilder builder() {
+ return new DistributionProcessorBuilder();
+ }
+
+ public static class DistributionProcessorBuilder {
+ private Logger logger;
+ private JReleaserModel model;
+ private String distributionName;
+ private String toolName;
+ private Path checksumDirectory;
+ private Path outputDirectory;
+
+ public DistributionProcessorBuilder logger(Logger logger) {
+ this.logger = requireNonNull(logger, "'logger' must not be null");
+ return this;
+ }
+
+ public DistributionProcessorBuilder model(JReleaserModel model) {
+ this.model = requireNonNull(model, "'model' must not be null");
+ return this;
+ }
+
+ public DistributionProcessorBuilder distributionName(String distributionName) {
+ this.distributionName = requireNonBlank(distributionName, "'distributionName' must not be blank");
+ return this;
+ }
+
+ public DistributionProcessorBuilder toolName(String toolName) {
+ this.toolName = requireNonBlank(toolName, "'toolName' must not be blank");
+ return this;
+ }
+
+ public DistributionProcessorBuilder checksumDirectory(Path checksumDirectory) {
+ this.checksumDirectory = requireNonNull(checksumDirectory, "'checksumDirectory' must not be null");
+ return this;
+ }
+
+ public DistributionProcessorBuilder outputDirectory(Path outputDirectory) {
+ this.outputDirectory = requireNonNull(outputDirectory, "'outputDirectory' must not be null");
+ return this;
+ }
+
+ public DistributionProcessor build() {
+ requireNonNull(logger, "'logger' must not be null");
+ requireNonNull(model, "'model' must not be null");
+ requireNonBlank(distributionName, "'distributionName' must not be blank");
+ requireNonBlank(toolName, "'toolName' must not be blank");
+ requireNonNull(checksumDirectory, "'checksumDirectory' must not be null");
+ requireNonNull(outputDirectory, "'outputDirectory' must not be null");
+ return new DistributionProcessor(logger, model, distributionName, toolName, checksumDirectory, outputDirectory);
+ }
+ }
+}
diff --git a/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/ProcessorUtils.java b/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/ProcessorUtils.java
new file mode 100644
index 00000000..222b592f
--- /dev/null
+++ b/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/ProcessorUtils.java
@@ -0,0 +1,120 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.tools;
+
+import com.github.mustachejava.DefaultMustacheFactory;
+import com.github.mustachejava.Mustache;
+import com.github.mustachejava.MustacheException;
+import com.github.mustachejava.MustacheFactory;
+import org.kordamp.jreleaser.model.Artifact;
+import org.kordamp.jreleaser.model.Distribution;
+import org.kordamp.jreleaser.model.JReleaserModel;
+import org.kordamp.jreleaser.model.Tool;
+import org.kordamp.jreleaser.util.Logger;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.file.Paths;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import static org.kordamp.jreleaser.util.StringUtils.capitalize;
+import static org.kordamp.jreleaser.util.StringUtils.isNotBlank;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+final class ProcessorUtils {
+ private ProcessorUtils() {
+ //noop
+ }
+
+ static boolean verifyAndAddArtifacts(Logger logger,
+ Map context,
+ JReleaserModel model,
+ Distribution distribution,
+ String artifactExtension,
+ T tool) throws ToolProcessingException {
+ List artifacts = distribution.getArtifacts().stream()
+ .filter(artifact -> artifact.getPath().endsWith(artifactExtension))
+ .collect(Collectors.toList());
+
+ if (artifacts.size() == 0) {
+ // we can't proceed
+ logger.warn("No suitable {} artifacts found in distribution {} to be packaged with ",
+ artifactExtension, distribution.getName(), capitalize(tool.getToolName()));
+ return false;
+ }
+
+ for (int i = 0; i < artifacts.size(); i++) {
+ Artifact artifact = artifacts.get(i);
+ String classifier = isNotBlank(artifact.getOsClassifier()) ? capitalize(artifact.getOsClassifier()) : "";
+ String artifactFileName = Paths.get(artifact.getPath()).getFileName().toString();
+ context.put("artifact" + classifier + "JavaVersion", artifact.getJavaVersion());
+ context.put("artifact" + classifier + "FileName", artifactFileName);
+ context.put("artifact" + classifier + "Hash", artifact.getHash());
+ Map newContext = new LinkedHashMap<>(context);
+ newContext.put("artifactFileName", artifactFileName);
+ String artifactUrl = applyTemplate(new StringReader(model.getRelease().getDownloadUrlFormat()), newContext, "downloadUrl");
+ context.put("artifact" + classifier + "Url", artifactUrl);
+
+ if (0 == i) {
+ context.put("distributionUrl", artifactUrl);
+ context.put("distributionSha256", artifact.getHash());
+ context.put("distributionJavaVersion", artifact.getJavaVersion());
+ }
+ }
+
+ return true;
+ }
+
+ static String applyTemplate(Reader reader, Map context, String toolName) {
+ StringWriter input = new StringWriter();
+ MustacheFactory mf = new MyMustacheFactory();
+ Mustache mustache = mf.compile(reader, toolName);
+ mustache.execute(input, context);
+ input.flush();
+ return input.toString();
+ }
+
+ static String applyTemplate(Reader reader, Map context) {
+ return applyTemplate(reader, context, UUID.randomUUID().toString());
+ }
+
+ private static class MyMustacheFactory extends DefaultMustacheFactory {
+ @Override
+ public void encode(String value, Writer writer) {
+ if (value.startsWith("!!") && value.endsWith("!!")) {
+ try {
+ writer.write(value.substring(2, value.length() - 2));
+ } catch (IOException e) {
+ throw new MustacheException("Failed to write value: " + value, e);
+ }
+ } else {
+ super.encode(value, writer);
+ }
+ }
+ }
+}
diff --git a/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/ScoopToolProcessor.java b/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/ScoopToolProcessor.java
new file mode 100644
index 00000000..5b94a152
--- /dev/null
+++ b/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/ScoopToolProcessor.java
@@ -0,0 +1,90 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.tools;
+
+import org.kordamp.jreleaser.model.Distribution;
+import org.kordamp.jreleaser.model.JReleaserModel;
+import org.kordamp.jreleaser.model.Project;
+import org.kordamp.jreleaser.model.Scoop;
+import org.kordamp.jreleaser.util.Logger;
+
+import java.io.StringReader;
+import java.nio.file.Path;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static org.kordamp.jreleaser.templates.TemplateUtils.trimTplExtension;
+import static org.kordamp.jreleaser.tools.ProcessorUtils.applyTemplate;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class ScoopToolProcessor extends AbstractToolProcessor {
+ public ScoopToolProcessor(Logger logger, JReleaserModel model, Scoop scoop) {
+ super(logger, model, scoop);
+ }
+
+ @Override
+ protected boolean doPackageDistribution(Distribution distribution, Map context) throws ToolProcessingException {
+ getLogger().debug("Tool {} does not require additional packaging", getToolName());
+ return true;
+ }
+
+ @Override
+ protected Set resolveByExtensionsFor(Distribution.DistributionType type) {
+ Set set = new LinkedHashSet<>();
+ set.add(".zip");
+ return set;
+ }
+
+ @Override
+ protected void fillToolProperties(Map context, Distribution distribution) throws ToolProcessingException {
+ context.put(Constants.KEY_SCOOP_CHECKVER_URL, resolveCheckverUrl(context));
+ context.put(Constants.KEY_SCOOP_AUTOUPDATE_URL, resolveAutoupdateUrl(context));
+ }
+
+ private Object resolveCheckverUrl(Map context) {
+ if (!getTool().getCheckverUrl().contains("{{")) {
+ return getTool().getCheckverUrl();
+ }
+ return applyTemplate(new StringReader(getTool().getCheckverUrl()), context);
+ }
+
+ private Object resolveAutoupdateUrl(Map context) {
+ if (!getTool().getAutoupdateUrl().contains("{{")) {
+ return getTool().getAutoupdateUrl();
+ }
+
+ Map copy = new LinkedHashMap<>(context);
+ copy.put(Constants.KEY_PROJECT_VERSION, "$version");
+ copy.put(Constants.KEY_ARTIFACT_FILE_NAME, copy.get("projectName") + "-$version.zip");
+ return applyTemplate(new StringReader(getTool().getAutoupdateUrl()), copy);
+ }
+
+ @Override
+ protected void writeFile(Project project, Distribution distribution, String content, Map context, String fileName)
+ throws ToolProcessingException {
+ Path outputDirectory = (Path) context.get(Constants.KEY_PREPARE_DIRECTORY);
+ Path outputFile = outputDirectory.resolve(trimTplExtension(fileName));
+
+ writeFile(content, outputFile);
+ }
+}
diff --git a/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/SnapToolProcessor.java b/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/SnapToolProcessor.java
new file mode 100644
index 00000000..e299468e
--- /dev/null
+++ b/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/SnapToolProcessor.java
@@ -0,0 +1,148 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.tools;
+
+import org.kordamp.jreleaser.model.Distribution;
+import org.kordamp.jreleaser.model.JReleaserModel;
+import org.kordamp.jreleaser.model.Project;
+import org.kordamp.jreleaser.model.Snap;
+import org.kordamp.jreleaser.util.FileUtils;
+import org.kordamp.jreleaser.util.Logger;
+import org.kordamp.jreleaser.util.OsUtils;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static org.kordamp.jreleaser.templates.TemplateUtils.trimTplExtension;
+import static org.kordamp.jreleaser.util.FileUtils.createDirectoriesWithFullAccess;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class SnapToolProcessor extends AbstractToolProcessor {
+ public SnapToolProcessor(Logger logger, JReleaserModel model, Snap snap) {
+ super(logger, model, snap);
+ }
+
+ @Override
+ protected boolean doPackageDistribution(Distribution distribution, Map context) throws ToolProcessingException {
+ if (OsUtils.isWindows()) {
+ getLogger().debug("Tool {} must not run on Windows", getToolName());
+ return false;
+ }
+
+ Path primeDirectory = createPackage(context);
+
+ if (!login(distribution, context)) {
+ getLogger().error("Could not log into snapcraft store");
+ return false;
+ }
+
+ return createSnap(distribution, context, primeDirectory);
+ }
+
+ @Override
+ protected Set resolveByExtensionsFor(Distribution.DistributionType type) {
+ Set set = new LinkedHashSet<>();
+ if (type == Distribution.DistributionType.BINARY) {
+ set.add(".tar.gz");
+ set.add(".tar");
+ } else if (type == Distribution.DistributionType.SINGLE_JAR) {
+ set.add(".jar");
+ }
+ return set;
+ }
+
+ @Override
+ protected void fillToolProperties(Map context, Distribution distribution) throws ToolProcessingException {
+ context.put(Constants.KEY_SNAP_BASE, getTool().getBase());
+ context.put(Constants.KEY_SNAP_GRADE, getTool().getGrade());
+ context.put(Constants.KEY_SNAP_CONFINEMENT, getTool().getConfinement());
+ context.put(Constants.KEY_SNAP_HAS_PLUGS, !getTool().getPlugs().isEmpty());
+ context.put(Constants.KEY_SNAP_PLUGS, getTool().getPlugs());
+ context.put(Constants.KEY_SNAP_HAS_SLOTS, !getTool().getSlots().isEmpty());
+ context.put(Constants.KEY_SNAP_SLOTS, getTool().getSlots());
+ context.put(Constants.KEY_SNAP_HAS_LOCAL_PLUGS, !getTool().getLocalPlugs().isEmpty());
+ context.put(Constants.KEY_SNAP_LOCAL_PLUGS, getTool().getLocalPlugs());
+ }
+
+ @Override
+ protected void writeFile(Project project, Distribution distribution, String content, Map context, String fileName)
+ throws ToolProcessingException {
+ fileName = trimTplExtension(fileName);
+
+ Path outputDirectory = (Path) context.get(Constants.KEY_PREPARE_DIRECTORY);
+ Path outputFile = outputDirectory.resolve(fileName);
+
+ writeFile(content, outputFile);
+ }
+
+ private Path createPackage(Map context) throws ToolProcessingException {
+ try {
+ Path prepareDirectory = (Path) context.get(Constants.KEY_PREPARE_DIRECTORY);
+ Path snapDirectory = prepareDirectory.resolve("snap");
+ Path packageDirectory = (Path) context.get(Constants.KEY_PACKAGE_DIRECTORY);
+ Path primeDirectory = packageDirectory.resolve("prime");
+ Path metaDirectory = primeDirectory.resolve("meta");
+ createDirectoriesWithFullAccess(metaDirectory);
+ if (FileUtils.copyFiles(getLogger(), snapDirectory, metaDirectory)) {
+ Files.move(metaDirectory.resolve("snapcraft.yaml"),
+ metaDirectory.resolve("snap.yaml"),
+ REPLACE_EXISTING);
+ return primeDirectory;
+ } else {
+ throw new ToolProcessingException("Could not copy files from " +
+ prepareDirectory.toAbsolutePath().toString() + " to " +
+ metaDirectory.toAbsolutePath().toString());
+ }
+ } catch (IOException e) {
+ throw new ToolProcessingException("Unexpected error when creating package", e);
+ }
+ }
+
+ private boolean login(Distribution distribution, Map context) throws ToolProcessingException {
+ List cmd = new ArrayList<>();
+ cmd.add("snapcraft");
+ cmd.add("login");
+ cmd.add("--with");
+ cmd.add(distribution.getSnap().getExportedLogin());
+ return executeCommand(cmd);
+ }
+
+ private boolean createSnap(Distribution distribution, Map context, Path primeDirectory) throws ToolProcessingException {
+ Path packageDirectory = (Path) context.get(Constants.KEY_PACKAGE_DIRECTORY);
+ String version = (String) context.get(Constants.KEY_PROJECT_VERSION);
+ String snapName = distribution.getName() + "-" + version + ".snap";
+
+ List cmd = new ArrayList<>();
+ cmd.add("snapcraft");
+ cmd.add("snap");
+ cmd.add(primeDirectory.toAbsolutePath().toString());
+ cmd.add("--output");
+ cmd.add(packageDirectory.resolve(snapName).toAbsolutePath().toString());
+ return executeCommand(cmd);
+ }
+}
diff --git a/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/ToolProcessingException.java b/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/ToolProcessingException.java
new file mode 100644
index 00000000..67a25fa0
--- /dev/null
+++ b/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/ToolProcessingException.java
@@ -0,0 +1,36 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.tools;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class ToolProcessingException extends Exception {
+ public ToolProcessingException(String message) {
+ super(message);
+ }
+
+ public ToolProcessingException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ToolProcessingException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/ToolProcessor.java b/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/ToolProcessor.java
new file mode 100644
index 00000000..4a9bc197
--- /dev/null
+++ b/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/ToolProcessor.java
@@ -0,0 +1,43 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.tools;
+
+import org.kordamp.jreleaser.model.Distribution;
+import org.kordamp.jreleaser.model.JReleaserModel;
+import org.kordamp.jreleaser.model.Tool;
+import org.kordamp.jreleaser.util.Logger;
+
+import java.util.Map;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public interface ToolProcessor {
+ T getTool();
+
+ String getToolName();
+
+ Logger getLogger();
+
+ JReleaserModel getModel();
+
+ boolean prepareDistribution(Distribution distribution, Map context) throws ToolProcessingException;
+
+ boolean packageDistribution(Distribution distribution, Map context) throws ToolProcessingException;
+}
diff --git a/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/ToolProcessors.java b/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/ToolProcessors.java
new file mode 100644
index 00000000..30660422
--- /dev/null
+++ b/core/jreleaser-tools/src/main/java/org/kordamp/jreleaser/tools/ToolProcessors.java
@@ -0,0 +1,46 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.tools;
+
+import org.kordamp.jreleaser.model.Brew;
+import org.kordamp.jreleaser.model.Chocolatey;
+import org.kordamp.jreleaser.model.JReleaserModel;
+import org.kordamp.jreleaser.model.Scoop;
+import org.kordamp.jreleaser.model.Snap;
+import org.kordamp.jreleaser.model.Tool;
+import org.kordamp.jreleaser.util.Logger;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class ToolProcessors {
+ public static ToolProcessor findProcessor(Logger logger, JReleaserModel model, T tool) {
+ if (tool instanceof Brew) {
+ return (ToolProcessor) new BrewToolProcessor(logger, model, (Brew) tool);
+ } else if (tool instanceof Chocolatey) {
+ return (ToolProcessor) new ChocolateyToolProcessor(logger, model, (Chocolatey) tool);
+ } else if (tool instanceof Scoop) {
+ return (ToolProcessor) new ScoopToolProcessor(logger, model, (Scoop) tool);
+ } else if (tool instanceof Snap) {
+ return (ToolProcessor) new SnapToolProcessor(logger, model, (Snap) tool);
+ }
+
+ throw new IllegalArgumentException("Unsupported tool " + tool);
+ }
+}
diff --git a/core/jreleaser-tools/src/main/module/module-info.java b/core/jreleaser-tools/src/main/module/module-info.java
new file mode 100644
index 00000000..45d46629
--- /dev/null
+++ b/core/jreleaser-tools/src/main/module/module-info.java
@@ -0,0 +1,26 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+module org.kordamp.jreleaser.tools {
+ exports org.kordamp.jreleaser.tools;
+ requires org.kordamp.jreleaser.model;
+ requires org.kordamp.jreleaser.util;
+ requires org.kordamp.jreleaser.templates;
+ requires com.github.mustachejava;
+ requires zt.exec;
+}
\ No newline at end of file
diff --git a/core/jreleaser-utils/gradle.properties b/core/jreleaser-utils/gradle.properties
new file mode 100644
index 00000000..3616b971
--- /dev/null
+++ b/core/jreleaser-utils/gradle.properties
@@ -0,0 +1,19 @@
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Copyright 2020 Andres Almiray.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+project_description = JReleaser utility classes
\ No newline at end of file
diff --git a/core/jreleaser-utils/jreleaser-utils.gradle b/core/jreleaser-utils/jreleaser-utils.gradle
new file mode 100644
index 00000000..57838687
--- /dev/null
+++ b/core/jreleaser-utils/jreleaser-utils.gradle
@@ -0,0 +1,21 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+dependencies {
+ api('kr.motd.maven:os-maven-plugin:1.6.2') { transitive = false }
+}
\ No newline at end of file
diff --git a/core/jreleaser-utils/src/main/java/org/kordamp/jreleaser/util/FileUtils.java b/core/jreleaser-utils/src/main/java/org/kordamp/jreleaser/util/FileUtils.java
new file mode 100644
index 00000000..c30c275d
--- /dev/null
+++ b/core/jreleaser-utils/src/main/java/org/kordamp/jreleaser/util/FileUtils.java
@@ -0,0 +1,142 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.util;
+
+import java.io.IOException;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileSystemLoopException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.FileVisitor;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.Set;
+
+import static java.nio.file.FileVisitResult.CONTINUE;
+import static java.nio.file.FileVisitResult.SKIP_SUBTREE;
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public final class FileUtils {
+ private FileUtils() {
+ //noop
+ }
+
+ public static void createDirectoriesWithFullAccess(Path path) throws IOException {
+ createDirectories(path, "rwxrwxrwx");
+ }
+
+ public static void createDirectories(Path path, String accessRights) throws IOException {
+ Set perms = PosixFilePermissions.fromString(accessRights);
+ FileAttribute> attr = PosixFilePermissions.asFileAttribute(perms);
+ Files.createDirectories(path, attr);
+ }
+
+ public static void grantFullAccess(Path path) throws IOException {
+ grantAccess(path, "rwxrwxrwx");
+ }
+
+ public static void grantAccess(Path path, String accessRights) throws IOException {
+ Set perms = PosixFilePermissions.fromString(accessRights);
+ Files.setPosixFilePermissions(path, perms);
+ }
+
+ public static boolean copyFiles(Logger logger, Path source, Path target) throws IOException {
+ FileTreeCopy copier = new FileTreeCopy(logger, source, target);
+ Files.walkFileTree(source, copier);
+ return copier.isSuccessful();
+ }
+
+ private static class FileTreeCopy implements FileVisitor {
+ private final Logger logger;
+ private final Path source;
+ private final Path target;
+ private boolean success = true;
+
+ FileTreeCopy(Logger logger, Path source, Path target) {
+ this.logger = logger;
+ this.source = source;
+ this.target = target;
+ }
+
+ public boolean isSuccessful() {
+ return success;
+ }
+
+ @Override
+ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
+ Path newdir = target.resolve(source.relativize(dir));
+ try {
+ Files.copy(dir, newdir);
+ FileUtils.grantFullAccess(newdir);
+ } catch (FileAlreadyExistsException ignored) {
+ // noop
+ } catch (IOException e) {
+ logger.error("Unable to create: {}", newdir, e);
+ success = false;
+ return SKIP_SUBTREE;
+ }
+ return CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
+ try {
+ Path newfile = target.resolve(source.relativize(file));
+ Files.copy(file, newfile, REPLACE_EXISTING);
+ FileUtils.grantFullAccess(newfile);
+ } catch (IOException e) {
+ logger.error("Unable to copy: {}", source, e);
+ success = false;
+ }
+ return CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
+ if (exc == null) {
+ Path newdir = target.resolve(source.relativize(dir));
+ try {
+ FileTime time = Files.getLastModifiedTime(dir);
+ Files.setLastModifiedTime(newdir, time);
+ } catch (IOException e) {
+ logger.warn("Unable to copy all attributes to: {}", newdir, e);
+ }
+ }
+ return CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFileFailed(Path file, IOException e) {
+ if (e instanceof FileSystemLoopException) {
+ logger.error("cycle detected: {}", file);
+ } else {
+ logger.error("Unable to copy: {}", file, e);
+ }
+ success = false;
+ return CONTINUE;
+ }
+ }
+}
diff --git a/core/jreleaser-utils/src/main/java/org/kordamp/jreleaser/util/Logger.java b/core/jreleaser-utils/src/main/java/org/kordamp/jreleaser/util/Logger.java
new file mode 100644
index 00000000..683a7c2a
--- /dev/null
+++ b/core/jreleaser-utils/src/main/java/org/kordamp/jreleaser/util/Logger.java
@@ -0,0 +1,48 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.util;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public interface Logger {
+ void debug(String message);
+
+ void info(String message);
+
+ void warn(String message);
+
+ void error(String message);
+
+ void debug(String message, Object... args);
+
+ void info(String message, Object... args);
+
+ void warn(String message, Object... args);
+
+ void error(String message, Object... args);
+
+ void debug(String message, Throwable throwable);
+
+ void info(String message, Throwable throwable);
+
+ void warn(String message, Throwable throwable);
+
+ void error(String message, Throwable throwable);
+}
diff --git a/core/jreleaser-utils/src/main/java/org/kordamp/jreleaser/util/OsUtils.java b/core/jreleaser-utils/src/main/java/org/kordamp/jreleaser/util/OsUtils.java
new file mode 100644
index 00000000..5093e6cb
--- /dev/null
+++ b/core/jreleaser-utils/src/main/java/org/kordamp/jreleaser/util/OsUtils.java
@@ -0,0 +1,92 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.util;
+
+import kr.motd.maven.os.Detector;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public final class OsUtils {
+ private static final OsDetector OS_DETECTOR = new OsDetector();
+
+ private OsUtils() {
+ //noop
+ }
+
+ public static boolean isWindows() {
+ return "windows".equals(OS_DETECTOR.get("os.detected.name"));
+ }
+
+ public static boolean isMac() {
+ return "osx".equals(OS_DETECTOR.get("os.detected.name"));
+ }
+
+ public static String getValue(String key) {
+ return OS_DETECTOR.get(key);
+ }
+
+ public static Set keySet() {
+ return OS_DETECTOR.getProperties().keySet();
+ }
+
+ public static Collection values() {
+ return OS_DETECTOR.getProperties().values();
+ }
+
+ public static Set> entrySet() {
+ return OS_DETECTOR.getProperties().entrySet();
+ }
+
+ private static final class OsDetector extends Detector {
+ private final Map props = new LinkedHashMap<>();
+
+ private OsDetector() {
+ Properties p = new Properties();
+ p.put("failOnUnknownOS", "false");
+ detect(p, Collections.emptyList());
+ p.stringPropertyNames().forEach(k -> props.put(k, p.getProperty(k)));
+ }
+
+ private Map getProperties() {
+ return Collections.unmodifiableMap(props);
+ }
+
+ private String get(String key) {
+ return props.get(key);
+ }
+
+ @Override
+ protected void log(String message) {
+ // quiet
+ }
+
+ @Override
+ protected void logProperty(String name, String value) {
+ // quiet
+ }
+ }
+}
diff --git a/core/jreleaser-utils/src/main/java/org/kordamp/jreleaser/util/StringUtils.java b/core/jreleaser-utils/src/main/java/org/kordamp/jreleaser/util/StringUtils.java
new file mode 100644
index 00000000..5127df1e
--- /dev/null
+++ b/core/jreleaser-utils/src/main/java/org/kordamp/jreleaser/util/StringUtils.java
@@ -0,0 +1,514 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.util;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public class StringUtils {
+ private static final String PROPERTY_SET_PREFIX = "set";
+ private static final String PROPERTY_GET_PREFIX = "get";
+ private static final Pattern GETTER_PATTERN_1 = Pattern.compile("^get[A-Z][\\w]*$");
+ private static final Pattern GETTER_PATTERN_2 = Pattern.compile("^is[A-Z][\\w]*$");
+ private static final Pattern SETTER_PATTERN = Pattern.compile("^set[A-Z][\\w]*$");
+ private static final String ERROR_METHOD_NULL = "Argument 'method' must not be null";
+
+ /**
+ * Capitalizes a String (makes the first char uppercase) taking care
+ * of blank strings and single character strings.
+ *
+ * @param str The String to be capitalized
+ * @return Capitalized version of the target string if it is not blank
+ */
+ public static String capitalize(String str) {
+ if (isBlank(str)) {
+ return str;
+ }
+
+ if (str.length() == 1) {
+ return str.toUpperCase();
+ }
+
+ return ((String) (str.substring(0, 1).toUpperCase(Locale.ENGLISH) + str.substring(1)));
+ }
+
+ public static String getFilenameExtension(String path) {
+ if (path == null) {
+ return null;
+ }
+
+ int extIndex = path.lastIndexOf(".");
+ if (extIndex == -1) {
+ return null;
+ }
+
+ int folderIndex = path.lastIndexOf(File.separator);
+ if (folderIndex > extIndex) {
+ return null;
+ }
+
+ return path.substring(extIndex + 1);
+ }
+
+ /**
+ * Retrieves the name of a setter for the specified property name
+ *
+ * @param propertyName The property name
+ * @return The setter equivalent
+ */
+ public static String getSetterName(String propertyName) {
+ return ((String) (PROPERTY_SET_PREFIX + capitalize(propertyName)));
+ }
+
+ /**
+ * Calculate the name for a getter method to retrieve the specified property
+ *
+ * @param propertyName The property name
+ * @return The name for the getter method for this property, if it were to exist, i.e. getConstraints
+ */
+ public static String getGetterName(String propertyName) {
+ return ((String) (PROPERTY_GET_PREFIX + capitalize(propertyName)));
+ }
+
+ /**
+ * Returns the class name for the given logical name and trailing name. For example "person" and "Controller" would evaluate to "PersonController"
+ *
+ * @param logicalName The logical name
+ * @param trailingName The trailing name
+ * @return The class name
+ */
+ public static String getClassName(String logicalName, String trailingName) {
+ if (isBlank(logicalName)) {
+ throw new IllegalArgumentException("Argument [logicalName] must not be null or blank");
+ }
+
+
+ String className = capitalize(logicalName);
+ if (trailingName != null) {
+ className = ((String) (className + trailingName));
+ }
+
+ return className;
+ }
+
+ /**
+ * Returns the class name representation of the given name
+ *
+ * @param name The name to convert
+ * @return The property name representation
+ */
+ public static String getClassNameRepresentation(String name) {
+ StringBuilder buf = new StringBuilder();
+ if (name != null && name.length() > 0) {
+ String[] tokens = name.split("[^\\w\\d]");
+ for (String token1 : tokens) {
+ String token = token1.trim();
+ buf.append(capitalize(token));
+ }
+ }
+
+ return buf.toString();
+ }
+
+ /**
+ * Converts foo-bar into FooBar. Empty and null strings are returned
+ * as-is.
+ *
+ * @param name The lower case hyphen separated name
+ * @return The class name equivalent.
+ */
+ public static String getClassNameForLowerCaseHyphenSeparatedName(String name) {
+ // Handle null and empty strings.
+ if (isBlank(name)) {
+ return name;
+ }
+
+ if (name.contains("-")) {
+ StringBuilder buf = new StringBuilder();
+ String[] tokens = name.split("-");
+ for (String token : tokens) {
+ if (token == null || token.length() == 0) {
+ continue;
+ }
+
+ buf.append(capitalize(token));
+ }
+
+ return buf.toString();
+ }
+
+ return capitalize(name);
+ }
+
+ /**
+ * Retrieves the logical class name of a Griffon artifact given the Griffon class
+ * and a specified trailing name
+ *
+ * @param clazz The class
+ * @param trailingName The trailing name such as "Controller" or "TagLib"
+ * @return The logical class name
+ */
+ public static String getLogicalName(Class> clazz, String trailingName) {
+ return getLogicalName(clazz.getName(), trailingName);
+ }
+
+ /**
+ * Retrieves the logical name of the class without the trailing name
+ *
+ * @param name The name of the class
+ * @param trailingName The trailing name
+ * @return The logical name
+ */
+ public static String getLogicalName(String name, String trailingName) {
+ if (isNotBlank(name) && isNotBlank(trailingName)) {
+ String shortName = getShortName(name);
+ if (shortName.endsWith(trailingName)) {
+ return shortName.substring(0, shortName.length() - trailingName.length());
+ }
+ }
+
+ return name;
+ }
+
+ public static String getLogicalPropertyName(String className, String trailingName) {
+ if (isNotBlank(className) && isNotBlank(trailingName) && className.length() == trailingName.length() + 1 && className.endsWith(trailingName)) {
+ return className.substring(0, 1).toLowerCase();
+ }
+
+ return getLogicalName(getPropertyName(className), trailingName);
+ }
+
+ /**
+ * Shorter version of getPropertyNameRepresentation
+ *
+ * @param name The name to convert
+ * @return The property name version
+ */
+ public static String getPropertyName(String name) {
+ return getPropertyNameRepresentation(name);
+ }
+
+ /**
+ * Shorter version of getPropertyNameRepresentation
+ *
+ * @param clazz The clazz to convert
+ * @return The property name version
+ */
+ public static String getPropertyName(Class> clazz) {
+ return getPropertyNameRepresentation(clazz);
+ }
+
+ /**
+ * Returns the property name representation of the given {@code Method}
+ *
+ * @param method The method to inspect
+ * @return The property name representation
+ * @since 3.0.0
+ */
+ public static String getPropertyName(Method method) {
+ Objects.requireNonNull(method, ERROR_METHOD_NULL);
+ String name = method.getName();
+ if (GETTER_PATTERN_1.matcher(name).matches() || SETTER_PATTERN.matcher(name).matches()) {
+ return uncapitalize(name.substring(3));
+ } else if (GETTER_PATTERN_2.matcher(name).matches()) {
+ return uncapitalize(name.substring(2));
+ }
+
+ return name;
+ }
+
+ /**
+ * Returns the property name equivalent for the specified class
+ *
+ * @param targetClass The class to get the property name for
+ * @return A property name representation of the class name (eg. MyClass becomes myClass)
+ */
+ public static String getPropertyNameRepresentation(Class> targetClass) {
+ String shortName = getShortName(targetClass);
+ return getPropertyNameRepresentation(shortName);
+ }
+
+ /**
+ * Returns the property name representation of the given name
+ *
+ * @param name The name to convert
+ * @return The property name representation
+ */
+ public static String getPropertyNameRepresentation(String name) {
+ if (isBlank(name)) {
+ return name;
+ }
+
+ // Strip any package from the name.
+ int pos = name.lastIndexOf(".");
+ if (pos != -1) {
+ name = name.substring(pos + 1);
+ }
+
+ // Check whether the name begins with two upper case letters.
+ if (name.length() > 1 && Character.isUpperCase(name.charAt(0)) && Character.isUpperCase(name.charAt(1))) {
+ return name;
+ }
+
+ String propertyName = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
+ if (propertyName.contains(" ")) {
+ propertyName = propertyName.replaceAll("\\s", "");
+ }
+
+ return propertyName;
+ }
+
+ /**
+ * Converts foo-bar into fooBar
+ *
+ * @param name The lower case hyphen separated name
+ * @return The property name equivalent
+ */
+ public static String getPropertyNameForLowerCaseHyphenSeparatedName(String name) {
+ return getPropertyName(getClassNameForLowerCaseHyphenSeparatedName(name));
+ }
+
+ /**
+ * Returns the class name without the package prefix
+ *
+ * @param targetClass The class to get a short name for
+ * @return The short name of the class
+ */
+ public static String getShortName(Class> targetClass) {
+ String className = targetClass.getName();
+ return getShortName(className);
+ }
+
+ /**
+ * Returns the class name without the package prefix
+ *
+ * @param className The class name to get a short name for
+ * @return The short name of the class
+ */
+ public static String getShortName(String className) {
+ if (isBlank(className)) {
+ return className;
+ }
+
+ int i = className.lastIndexOf(".");
+ if (i > -1) {
+ className = className.substring(i + 1, className.length());
+ }
+
+ return className;
+ }
+
+ /**
+ * Converts a property name into its natural language equivalent eg ('firstName' becomes 'First Name')
+ *
+ * @param name The property name to convert
+ * @return The converted property name
+ */
+ public static String getNaturalName(String name) {
+ name = getShortName(name);
+ if (isBlank(name)) {
+ return name;
+ }
+
+ List words = new ArrayList();
+ int i = 0;
+ char[] chars = name.toCharArray();
+ for (char c : chars) {
+ String w;
+ if (i >= words.size()) {
+ w = "";
+ words.add(i, w);
+ } else {
+ w = words.get(i);
+ }
+
+ if (Character.isLowerCase(c) || Character.isDigit(c)) {
+ if (Character.isLowerCase(c) && w.length() == 0) {
+ c = Character.toUpperCase(c);
+ } else if (w.length() > 1 && Character.isUpperCase(w.charAt(w.length() - 1))) {
+ w = "";
+ words.add(++i, w);
+ }
+
+ words.set(i, w + c);
+ } else if (Character.isUpperCase(c)) {
+ if ((i == 0 && w.length() == 0) || Character.isUpperCase(w.charAt(w.length() - 1))) {
+ words.set(i, w + c);
+ } else {
+ words.add(++i, String.valueOf(c));
+ }
+ }
+ }
+
+ StringBuilder buf = new StringBuilder();
+ for (Iterator j = words.iterator(); j.hasNext(); ) {
+ String word = j.next();
+ buf.append(word);
+ if (j.hasNext()) {
+ buf.append(" ");
+ }
+ }
+
+ return buf.toString();
+ }
+
+ /**
+ * Determines whether a given string is null, empty,
+ * or only contains whitespace. If it contains anything other than
+ * whitespace then the string is not considered to be blank and the
+ * method returns false.
+ *
+ * @param str The string to test.
+ * @return true if the string is null, or
+ * blank.
+ */
+ public static boolean isBlank(String str) {
+ if (str == null || str.length() == 0) {
+ return true;
+ }
+
+ for (char c : str.toCharArray()) {
+ if (!Character.isWhitespace(c)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Determines whether a given string is not null, empty,
+ * or only contains whitespace. If it contains anything other than
+ * whitespace then the string is not considered to be blank and the
+ * method returns true.
+ *
+ * @param str The string to test.
+ * @return true if the string is not null, nor
+ * blank.
+ */
+ public static boolean isNotBlank(String str) {
+ return ((boolean) (!isBlank(str)));
+ }
+
+ /**
+ * Checks that the specified String is not {@code blank}. This
+ * method is designed primarily for doing parameter validation in methods
+ * and constructors, as demonstrated below:
+ *
+ * Foo(String str) {* this.str = GriffonNameUtils.requireNonBlank(str)
+ * }*
+ *
+ * @param str the String to check for blank
+ * @return {@code str} if not {@code blank}
+ * @throws IllegalArgumentException if {@code str} is {@code blank}
+ */
+ public static String requireNonBlank(String str) {
+ if (isBlank(str)) {
+ throw new IllegalArgumentException();
+ }
+
+ return str;
+ }
+
+ /**
+ * Checks that the specified String is not {@code blank} and
+ * throws a customized {@link IllegalArgumentException} if it is. This method
+ * is designed primarily for doing parameter validation in methods and
+ * constructors with multiple parameters, as demonstrated below:
+ *
+ * Foo(String str) {* this.str = GriffonNameUtils.requireNonBlank(str, "str must not be null")
+ * }*
+ *
+ * @param str the String to check for blank
+ * @param message detail message to be used in the event that a {@code
+ * IllegalArgumentException} is thrown
+ * @return {@code str} if not {@code blank}
+ * @throws IllegalArgumentException if {@code str} is {@code blank}
+ */
+ public static String requireNonBlank(String str, String message) {
+ if (isBlank(str)) {
+ throw new IllegalArgumentException(message);
+ }
+
+ return str;
+ }
+
+ /**
+ * Retrieves the hyphenated name representation of the supplied class. For example
+ * MyFunkyGriffonThingy would be my-funky-griffon-thingy.
+ *
+ * @param clazz The class to convert
+ * @return The hyphenated name representation
+ */
+ public static String getHyphenatedName(Class> clazz) {
+ if (clazz == null) {
+ return null;
+ }
+
+ return getHyphenatedName(clazz.getName());
+ }
+
+ /**
+ * Retrieves the hyphenated name representation of the given class name.
+ * For example MyFunkyGriffonThingy would be my-funky-griffon-thingy.
+ *
+ * @param name The class name to convert.
+ * @return The hyphenated name representation.
+ */
+ public static String getHyphenatedName(String name) {
+ if (isBlank(name)) {
+ return name;
+ }
+
+ if (name.endsWith(".groovy")) {
+ name = name.substring(0, name.length() - 7);
+ }
+
+ String naturalName = getNaturalName(getShortName(name));
+ return naturalName.replaceAll("\\s", "-").toLowerCase();
+ }
+
+ /**
+ * Uncapitalizes a String (makes the first char lowercase) taking care
+ * of blank strings and single character strings.
+ *
+ * @param str The String to be uncapitalized
+ * @return Uncapitalized version of the target string if it is not blank
+ */
+ public static String uncapitalize(String str) {
+ if (isBlank(str)) {
+ return str;
+ }
+
+ if (str.length() == 1) {
+ return String.valueOf(Character.toLowerCase(str.charAt(0)));
+ }
+
+ return Character.toLowerCase(str.charAt(0)) + str.substring(1);
+ }
+}
diff --git a/core/jreleaser-utils/src/main/module/module-info.java b/core/jreleaser-utils/src/main/module/module-info.java
new file mode 100644
index 00000000..24f59263
--- /dev/null
+++ b/core/jreleaser-utils/src/main/module/module-info.java
@@ -0,0 +1,22 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+module org.kordamp.jreleaser.util {
+ exports org.kordamp.jreleaser.util;
+ requires os.maven.plugin;
+}
\ No newline at end of file
diff --git a/docs/guide/guide.gradle b/docs/guide/guide.gradle
new file mode 100644
index 00000000..41b97125
--- /dev/null
+++ b/docs/guide/guide.gradle
@@ -0,0 +1,32 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+configurations {
+ asciidoctorExtensions
+}
+
+dependencies {
+ asciidoctorExtensions 'com.bmuschko:asciidoctorj-tabbed-code-extension:0.3'
+}
+
+asciidoctor {
+ configurations 'asciidoctorExtensions'
+ attributes = [
+ 'gradle-version': project.gradle.gradleVersion,
+ 'source-highlighter': 'prettify'
+ ]
+}
diff --git a/docs/guide/src/docs/asciidoc/_links.adoc b/docs/guide/src/docs/asciidoc/_links.adoc
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/guide/src/docs/asciidoc/index.adoc b/docs/guide/src/docs/asciidoc/index.adoc
new file mode 100644
index 00000000..c1ef5dac
--- /dev/null
+++ b/docs/guide/src/docs/asciidoc/index.adoc
@@ -0,0 +1,23 @@
+ifndef::imagesdir[]
+:imagesdir: ../resources/images
+endif::[]
+ifndef::includedir[]
+:includedir: .
+endif::[]
+= {project-title}
+:author: {project-author}
+:revnumber: {project-version}
+:toclevels: 4
+:docinfo1:
+
+include::{includedir}/_links.adoc[]
+
+:leveloffset: 1
+include::{includedir}/introduction.adoc[]
+include::{includedir}/usage.adoc[]
+
+= Links
+
+link:api/index.html[Javadoc, window="_blank"]
+
+link:api-html/index.html[Source, window="_blank"]
\ No newline at end of file
diff --git a/docs/guide/src/docs/asciidoc/introduction.adoc b/docs/guide/src/docs/asciidoc/introduction.adoc
new file mode 100644
index 00000000..7534dc52
--- /dev/null
+++ b/docs/guide/src/docs/asciidoc/introduction.adoc
@@ -0,0 +1,5 @@
+
+[[_introduction]]
+= Introduction
+
+Lorem ipsum dolor sit amet
diff --git a/docs/guide/src/docs/asciidoc/usage.adoc b/docs/guide/src/docs/asciidoc/usage.adoc
new file mode 100644
index 00000000..2b2d8709
--- /dev/null
+++ b/docs/guide/src/docs/asciidoc/usage.adoc
@@ -0,0 +1,5 @@
+
+[[_usage]]
+= Usage
+
+Lorem ipsum dolor sit amet
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 00000000..38e3179d
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,49 @@
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Copyright 2020 Andres Almiray.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+group = org.kordamp.jreleaser
+version = 0.1.0-SNAPSHOT
+#sourceCompatibility = 1.8
+#targetCompatibility = 1.8
+javaCompatibility = 1.8
+
+kordampPluginVersion = 0.42.0
+kordampBuildVersion = 2.1.0
+gitPluginVersion = 3.0.0
+checksumPluginVersion = 1.1.0
+beryxJarPluginVersion = 1.2.0
+
+asmVersion = 8.0.1
+jacksonVersion = 2.11.3
+jipsyVersion = 0.6.0
+junitVersion = 5.7.0
+hamcrestVersion = 2.2
+mavenVersion = 3.6.0
+mustacheVersion = 0.9.7
+okhttpVersion = 3.14.9
+picocliVersion = 4.5.2
+plexusVersion = 3.1.0
+tikaVersion = 1.24.1
+wiremockVersion = 2.27.2
+slf4jVersion = 1.7.30
+snakeYamlVersion = 1.27
+ztexecVersion = 1.12
+
+org.gradle.daemon = true
+org.gradle.caching = true
+org.gradle.parallel = false
diff --git a/gradle/LICENSE_HEADER b/gradle/LICENSE_HEADER
new file mode 100644
index 00000000..aa8903cb
--- /dev/null
+++ b/gradle/LICENSE_HEADER
@@ -0,0 +1,15 @@
+SPDX-License-Identifier: Apache-2.0
+
+Copyright ${copyrightYear} ${author}.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..e708b1c0
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..be52383e
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 00000000..4f906e0c
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 00000000..107acd32
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/plugins/jreleaser-gradle-plugin/gradle.properties b/plugins/jreleaser-gradle-plugin/gradle.properties
new file mode 100644
index 00000000..34906283
--- /dev/null
+++ b/plugins/jreleaser-gradle-plugin/gradle.properties
@@ -0,0 +1,24 @@
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Copyright 2020 Andres Almiray.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+pluginId = org.kordamp.jreleaser
+pluginShortName = jreleaser
+pluginDisplayName = JReleaser Gradle Plugin
+pluginImplementationClass = org.kordamp.jreleaser.gradle.plugin.JReleaserPlugin
+project_description = JReleaser Gradle Plugin
\ No newline at end of file
diff --git a/plugins/jreleaser-gradle-plugin/jreleaser-gradle-plugin.gradle b/plugins/jreleaser-gradle-plugin/jreleaser-gradle-plugin.gradle
new file mode 100644
index 00000000..0bec90c2
--- /dev/null
+++ b/plugins/jreleaser-gradle-plugin/jreleaser-gradle-plugin.gradle
@@ -0,0 +1,58 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+plugins {
+ id 'groovy'
+ id 'org.kordamp.gradle.groovy-project'
+ id 'org.kordamp.gradle.plugin'
+}
+
+config {
+ bintray {
+ enabled = true
+ }
+
+ plugins {
+ plugin {
+ name = project.pluginShortName
+ id = project.pluginId
+ implementationClass = project.pluginImplementationClass
+ }
+ }
+}
+
+dependencies {
+ compileOnly gradleApi()
+
+ api project(':jreleaser-model')
+ api project(':jreleaser-tools')
+ api project(':jreleaser-templates')
+ api "org.kordamp.gradle:base-gradle-plugin:$kordampPluginVersion"
+ api "gradle.plugin.org.gradle.crypto:checksum:$checksumPluginVersion"
+}
+
+processResources {
+ inputs.property('version', project.version)
+ filesMatching(['**/*.properties']) {
+ expand(
+ 'version': project.version,
+ 'id': 'jrleaser',
+ 'name': project.pluginDisplayName
+ )
+ }
+}
diff --git a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/Banner.groovy b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/Banner.groovy
new file mode 100644
index 00000000..ae46a0f7
--- /dev/null
+++ b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/Banner.groovy
@@ -0,0 +1,89 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.gradle.plugin
+
+import groovy.transform.CompileStatic
+import org.gradle.BuildAdapter
+import org.gradle.BuildResult
+import org.gradle.api.Project
+
+import java.text.MessageFormat
+
+/**
+ *
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CompileStatic
+final class Banner {
+ private final ResourceBundle bundle = ResourceBundle.getBundle(Banner.name)
+ private final String productVersion = bundle.getString('product.version')
+ private final String productId = bundle.getString('product.id')
+ private final String productName = bundle.getString('product.name')
+ private final String banner = MessageFormat.format(bundle.getString('product.banner'), productName, productVersion)
+ private final List visited = []
+
+ private static final Banner b = new Banner()
+
+ private Banner() {
+ // nooop
+ }
+
+ static void display(Project project) {
+ if (b.visited.contains(project.rootProject.name)) {
+ return
+ }
+ b.visited.add(project.rootProject.name)
+ project.gradle.addBuildListener(new BuildAdapter() {
+ @Override
+ void buildFinished(BuildResult result) {
+ b.visited.clear()
+ }
+ })
+
+ File parent = new File(project.gradle.gradleUserHomeDir, 'caches')
+ File markerFile = b.getMarkerFile(parent)
+ if (!markerFile.exists()) {
+ markerFile.parentFile.mkdirs()
+ markerFile.text = '1'
+ println(b.banner)
+ } else {
+ try {
+ int count = Integer.parseInt(markerFile.text)
+ if (count < 3) {
+ println(b.banner)
+ }
+ markerFile.text = (count + 1) + ''
+ } catch (NumberFormatException e) {
+ markerFile.text = '1'
+ println(b.banner)
+ }
+ }
+ }
+
+ private File getMarkerFile(File parent) {
+ new File(parent,
+ 'kordamp' +
+ File.separator +
+ productId +
+ File.separator +
+ productVersion +
+ File.separator +
+ 'marker.txt')
+ }
+}
diff --git a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/JReleaserExtension.groovy b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/JReleaserExtension.groovy
new file mode 100644
index 00000000..edfa7401
--- /dev/null
+++ b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/JReleaserExtension.groovy
@@ -0,0 +1,51 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.gradle.plugin
+
+import groovy.transform.CompileStatic
+import org.gradle.api.Action
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.provider.Property
+import org.kordamp.jreleaser.gradle.plugin.dsl.Distribution
+import org.kordamp.jreleaser.gradle.plugin.dsl.Packagers
+import org.kordamp.jreleaser.gradle.plugin.dsl.Project
+import org.kordamp.jreleaser.gradle.plugin.dsl.Release
+
+/**
+ *
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CompileStatic
+interface JReleaserExtension {
+ Property getEnabled()
+
+ Project getProject()
+
+ Release getRelease()
+
+ Packagers getPackagers()
+
+ NamedDomainObjectContainer getDistributions()
+
+ void project(Action super Project> action)
+
+ void release(Action super Release> action)
+
+ void packagers(Action super Packagers> action)
+}
diff --git a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/JReleaserPlugin.groovy b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/JReleaserPlugin.groovy
new file mode 100644
index 00000000..8322be99
--- /dev/null
+++ b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/JReleaserPlugin.groovy
@@ -0,0 +1,86 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.gradle.plugin
+
+import groovy.transform.CompileDynamic
+import groovy.transform.CompileStatic
+import org.gradle.api.Action
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.file.Directory
+import org.gradle.api.provider.Provider
+import org.kordamp.jreleaser.gradle.plugin.internal.JReleaserExtensionImpl
+import org.kordamp.jreleaser.gradle.plugin.internal.JReleaserProjectConfigurer
+
+/**
+ *
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CompileStatic
+class JReleaserPlugin implements Plugin {
+ @Override
+ void apply(Project project) {
+ Banner.display(project)
+
+ Provider nameProvider = project.provider({ -> project.name })
+ Provider descriptionProvider = project.provider({ -> project.description })
+ Provider versionProvider = project.provider({ -> String.valueOf(project.version) })
+ Provider distributionsDirProvider = project.provider({ ->
+ project.layout.projectDirectory.dir('src/distributions')
+ })
+ project.extensions.create(JReleaserExtension, 'jreleaser', JReleaserExtensionImpl,
+ project.objects, nameProvider, descriptionProvider, versionProvider, distributionsDirProvider)
+
+ project.afterEvaluate(new Action() {
+ @Override
+ void execute(Project p) {
+ JReleaserExtension extension = project.extensions.findByType(JReleaserExtension)
+ if (!extension.enabled.get()) return
+
+ if (hasKordampBasePluginApplied(p)) {
+ registerAllProjectsEvaluatedListener(p)
+ } else {
+ configureJReleaser(p)
+ }
+ }
+ })
+ }
+
+ private void configureJReleaser(Project project) {
+ JReleaserProjectConfigurer.configure(project)
+ }
+
+ private boolean hasKordampBasePluginApplied(Project project) {
+ project.rootProject.plugins.findPlugin('org.kordamp.gradle.base')
+ }
+
+ @CompileDynamic
+ private void registerAllProjectsEvaluatedListener(Project project) {
+ Class c = Class.forName('org.kordamp.jreleaser.gradle.plugin.internal.JReleaserAllProjectsEvaluatedListener')
+ def listener = c.getConstructor().newInstance()
+ listener.runnable = { ->
+ Class.forName('org.kordamp.jreleaser.gradle.plugin.internal.KordampJReleaserAdapter')
+ .adapt(project)
+ configureJReleaser(project)
+ }
+
+ Class m = Class.forName('org.kordamp.gradle.listener.ProjectEvaluationListenerManager')
+ m.addAllProjectsEvaluatedListener(project, listener)
+ }
+}
diff --git a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Artifact.groovy b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Artifact.groovy
new file mode 100644
index 00000000..c55333da
--- /dev/null
+++ b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Artifact.groovy
@@ -0,0 +1,34 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.gradle.plugin.dsl
+
+import groovy.transform.CompileStatic
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.provider.Property
+
+/**
+ *
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CompileStatic
+interface Artifact {
+ RegularFileProperty getPath()
+
+ Property getOsClassifier()
+}
\ No newline at end of file
diff --git a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Brew.groovy b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Brew.groovy
new file mode 100644
index 00000000..953c3514
--- /dev/null
+++ b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Brew.groovy
@@ -0,0 +1,29 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.gradle.plugin.dsl
+
+import groovy.transform.CompileStatic
+
+/**
+ *
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CompileStatic
+interface Brew extends BrewPackager, Tool {
+}
\ No newline at end of file
diff --git a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/BrewPackager.groovy b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/BrewPackager.groovy
new file mode 100644
index 00000000..23df3c45
--- /dev/null
+++ b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/BrewPackager.groovy
@@ -0,0 +1,35 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.gradle.plugin.dsl
+
+import groovy.transform.CompileStatic
+import org.gradle.api.provider.MapProperty
+
+/**
+ *
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CompileStatic
+interface BrewPackager extends PackagerTool {
+ MapProperty getDependencies()
+
+ void addDependency(String key, String value)
+
+ void addDependency(String key)
+}
\ No newline at end of file
diff --git a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Chocolatey.groovy b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Chocolatey.groovy
new file mode 100644
index 00000000..b3580d74
--- /dev/null
+++ b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Chocolatey.groovy
@@ -0,0 +1,29 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.gradle.plugin.dsl
+
+import groovy.transform.CompileStatic
+
+/**
+ *
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CompileStatic
+interface Chocolatey extends ChocolateyPackager, Tool {
+}
\ No newline at end of file
diff --git a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/ChocolateyPackager.groovy b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/ChocolateyPackager.groovy
new file mode 100644
index 00000000..1101e69d
--- /dev/null
+++ b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/ChocolateyPackager.groovy
@@ -0,0 +1,29 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.gradle.plugin.dsl
+
+import groovy.transform.CompileStatic
+
+/**
+ *
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CompileStatic
+interface ChocolateyPackager extends PackagerTool {
+}
\ No newline at end of file
diff --git a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Distribution.groovy b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Distribution.groovy
new file mode 100644
index 00000000..0d6a9b05
--- /dev/null
+++ b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Distribution.groovy
@@ -0,0 +1,60 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.gradle.plugin.dsl
+
+import groovy.transform.CompileStatic
+import org.gradle.api.Action
+import org.gradle.api.provider.ListProperty
+import org.gradle.api.provider.Property
+import org.kordamp.jreleaser.model.Distribution.DistributionType
+
+/**
+ *
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CompileStatic
+interface Distribution extends ExtraProperties {
+ Property getDistributionType()
+
+ Property getExecutable()
+
+ ListProperty getTags()
+
+ void setDistributionType(String distributionType)
+
+ void addTag(String tag)
+
+ void artifact(Action super Artifact> action)
+
+ Brew getBrew()
+
+ Chocolatey getChocolatey()
+
+ Scoop getScoop()
+
+ Snap getSnap()
+
+ void brew(Action super Brew> action)
+
+ void chocolatey(Action super Chocolatey> action)
+
+ void scoop(Action super Scoop> action)
+
+ void snap(Action super Snap> action)
+}
\ No newline at end of file
diff --git a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/ExtraProperties.java b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/ExtraProperties.java
new file mode 100644
index 00000000..dfdfa20b
--- /dev/null
+++ b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/ExtraProperties.java
@@ -0,0 +1,28 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.gradle.plugin.dsl;
+
+import org.gradle.api.provider.MapProperty;
+
+/**
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+public interface ExtraProperties {
+ MapProperty getExtraProperties();
+}
diff --git a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/PackagerTool.groovy b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/PackagerTool.groovy
new file mode 100644
index 00000000..650b4a33
--- /dev/null
+++ b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/PackagerTool.groovy
@@ -0,0 +1,31 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.gradle.plugin.dsl
+
+import groovy.transform.CompileStatic
+import org.gradle.api.provider.Property
+
+/**
+ *
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CompileStatic
+interface PackagerTool extends ExtraProperties {
+ Property getEnabled()
+}
\ No newline at end of file
diff --git a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Packagers.groovy b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Packagers.groovy
new file mode 100644
index 00000000..16321c3f
--- /dev/null
+++ b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Packagers.groovy
@@ -0,0 +1,45 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.gradle.plugin.dsl
+
+import groovy.transform.CompileStatic
+import org.gradle.api.Action
+
+/**
+ *
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CompileStatic
+interface Packagers {
+ BrewPackager getBrew()
+
+ ChocolateyPackager getChocolatey()
+
+ ScoopPackager getScoop()
+
+ SnapPackager getSnap()
+
+ void brew(Action super BrewPackager> action)
+
+ void chocolatey(Action super ChocolateyPackager> action)
+
+ void scoop(Action super ScoopPackager> action)
+
+ void snap(Action super SnapPackager> action)
+}
\ No newline at end of file
diff --git a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Plug.groovy b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Plug.groovy
new file mode 100644
index 00000000..c33bec72
--- /dev/null
+++ b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Plug.groovy
@@ -0,0 +1,33 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.gradle.plugin.dsl
+
+import groovy.transform.CompileStatic
+import org.gradle.api.provider.MapProperty
+
+/**
+ *
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CompileStatic
+interface Plug {
+ MapProperty getAttributes()
+
+ void addAttribute(String key, String value)
+}
\ No newline at end of file
diff --git a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Project.groovy b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Project.groovy
new file mode 100644
index 00000000..a82adb9a
--- /dev/null
+++ b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Project.groovy
@@ -0,0 +1,48 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.gradle.plugin.dsl
+
+import groovy.transform.CompileStatic
+import org.gradle.api.provider.ListProperty
+import org.gradle.api.provider.Property
+
+/**
+ *
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CompileStatic
+interface Project extends ExtraProperties {
+ Property getName()
+
+ Property getVersion()
+
+ Property getDescription()
+
+ Property getWebsite()
+
+ Property getLicense()
+
+ ListProperty getAuthors()
+
+ ListProperty getTags()
+
+ void addAuthor(String name)
+
+ void addTag(String tag)
+}
\ No newline at end of file
diff --git a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Release.groovy b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Release.groovy
new file mode 100644
index 00000000..b527ef91
--- /dev/null
+++ b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/kordamp/jreleaser/gradle/plugin/dsl/Release.groovy
@@ -0,0 +1,67 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright 2020 Andres Almiray.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kordamp.jreleaser.gradle.plugin.dsl
+
+import groovy.transform.CompileStatic
+import org.gradle.api.provider.Property
+
+/**
+ *
+ * @author Andres Almiray
+ * @since 0.1.0
+ */
+@CompileStatic
+interface Release {
+ Property getRepoType()
+
+ Property getRepoHost()
+
+ Property getRepoOwner()
+
+ Property getRepoName()
+
+ Property getDownloadUrlFormat()
+
+ Property