[brew] support Linux formulae. Fixes #215

This commit is contained in:
Andres Almiray
2021-07-22 21:07:02 +02:00
parent 371feef069
commit b036ddfe23
13 changed files with 233 additions and 14 deletions

View File

@@ -43,6 +43,7 @@ public class Brew extends AbstractRepositoryTool {
private String formulaName;
private String cachedFormulaName;
private Boolean multiPlatform;
public Brew() {
super(NAME);
@@ -51,6 +52,7 @@ public class Brew extends AbstractRepositoryTool {
void setAll(Brew brew) {
super.setAll(brew);
this.formulaName = brew.formulaName;
this.multiPlatform = brew.multiPlatform;
setTap(brew.tap);
setDependenciesAsList(brew.dependencies);
setLivecheck(brew.livecheck);
@@ -92,6 +94,18 @@ public class Brew extends AbstractRepositoryTool {
this.formulaName = formulaName;
}
public boolean isMultiPlatform() {
return multiPlatform != null && multiPlatform;
}
public void setMultiPlatform(Boolean multiPlatform) {
this.multiPlatform = multiPlatform;
}
public boolean isMultiPlatformSet() {
return multiPlatform != null;
}
public HomebrewTap getTap() {
return tap;
}
@@ -160,6 +174,7 @@ public class Brew extends AbstractRepositoryTool {
protected void asMap(boolean full, Map<String, Object> props) {
super.asMap(full, props);
props.put("formulaName", formulaName);
props.put("multiPlatform", isMultiPlatform());
props.put("tap", tap.asMap(full));
props.put("dependencies", dependencies);
props.put("livecheck", livecheck);
@@ -173,6 +188,9 @@ public class Brew extends AbstractRepositoryTool {
@Override
public boolean supportsPlatform(String platform) {
if (isMultiPlatform()) {
return isBlank(platform) || PlatformUtils.isMac(platform) || PlatformUtils.isLinux(platform);
}
return isBlank(platform) || PlatformUtils.isMac(platform);
}

View File

@@ -37,7 +37,7 @@ import static org.jreleaser.util.StringUtils.isNotBlank;
public class Cask implements Domain {
private final List<CaskItem> uninstall = new ArrayList<>();
private final List<CaskItem> zap = new ArrayList<>();
protected boolean enabled;
protected Boolean enabled;
private String name;
private String displayName;
private String pkgName;
@@ -69,7 +69,11 @@ public class Cask implements Domain {
}
public boolean isEnabled() {
return enabled;
return enabled != null && enabled;
}
public boolean isEnabledSet() {
return enabled != null;
}
public void setEnabled(boolean enabled) {

View File

@@ -102,7 +102,21 @@ public abstract class BrewValidator extends Validator {
tap.getToken(),
service.getResolvedToken()));
if (!tool.isMultiPlatformSet() && parentTool.isMultiPlatformSet()) {
tool.setMultiPlatform(parentTool.isMultiPlatform());
}
if (tool.isMultiPlatform() &&
(distribution.getType() == Distribution.DistributionType.SINGLE_JAR ||
distribution.getType() == Distribution.DistributionType.JAVA_BINARY ||
distribution.getType() == Distribution.DistributionType.NATIVE_PACKAGE)) {
tool.setMultiPlatform(false);
}
if (tool.isMultiPlatform()) {
tool.getCask().disable();
}
validateCask(context, distribution, tool, errors);
if (!tool.getCask().isEnabled()) {
validateArtifactPlatforms(context, distribution, tool, errors);
}
@@ -114,6 +128,12 @@ public abstract class BrewValidator extends Validator {
return;
}
Cask cask = tool.getCask();
if (cask.isEnabledSet() && !cask.isEnabled()) {
return;
}
context.getLogger().debug("distribution.{}.brew.cask", distribution.getName());
// look for a .dmg, .pkg. or .zip
@@ -133,7 +153,6 @@ public abstract class BrewValidator extends Validator {
}
}
Cask cask = tool.getCask();
if (dmgFound == 0 && pkgFound == 0 && zipFound == 0) {
// no artifacts found, disable cask

View File

@@ -0,0 +1,30 @@
class {{brewFormulaName}} < Formula
desc "{{projectDescription}}"
homepage "{{projectWebsite}}"
version "{{projectVersion}}"
license "{{projectLicense}}"
bottle :unneeded
{{brewMultiPlatform}}
{{#brewHasLivecheck}}
livecheck do
{{#brewLivecheck}}
{{.}}
{{/brewLivecheck}}
end
{{/brewHasLivecheck}}
{{#brewDependencies}}
depends_on {{.}}
{{/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

View File

@@ -0,0 +1,30 @@
class {{brewFormulaName}} < Formula
desc "{{projectDescription}}"
homepage "{{projectWebsite}}"
version "{{projectVersion}}"
license "{{projectLicense}}"
bottle :unneeded
{{brewMultiPlatform}}
{{#brewHasLivecheck}}
livecheck do
{{#brewLivecheck}}
{{.}}
{{/brewLivecheck}}
end
{{/brewHasLivecheck}}
{{#brewDependencies}}
depends_on {{.}}
{{/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

View File

@@ -0,0 +1,30 @@
class {{brewFormulaName}} < Formula
desc "{{projectDescription}}"
homepage "{{projectWebsite}}"
version "{{projectVersion}}"
license "{{projectLicense}}"
bottle :unneeded
{{brewMultiPlatform}}
{{#brewHasLivecheck}}
livecheck do
{{#brewLivecheck}}
{{.}}
{{/brewLivecheck}}
end
{{/brewHasLivecheck}}
{{#brewDependencies}}
depends_on {{.}}
{{/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

View File

@@ -25,11 +25,15 @@ import org.jreleaser.model.GitService;
import org.jreleaser.model.JReleaserContext;
import org.jreleaser.model.Project;
import org.jreleaser.model.tool.spi.ToolProcessingException;
import org.jreleaser.util.Algorithm;
import org.jreleaser.util.Constants;
import org.jreleaser.util.MustacheUtils;
import org.jreleaser.util.PlatformUtils;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -37,6 +41,7 @@ import static org.jreleaser.templates.TemplateUtils.trimTplExtension;
import static org.jreleaser.util.MustacheUtils.applyTemplate;
import static org.jreleaser.util.MustacheUtils.passThrough;
import static org.jreleaser.util.StringUtils.getFilename;
import static org.jreleaser.util.StringUtils.isBlank;
import static org.jreleaser.util.StringUtils.isNotBlank;
import static org.jreleaser.util.StringUtils.isTrue;
@@ -45,6 +50,21 @@ import static org.jreleaser.util.StringUtils.isTrue;
* @since 0.1.0
*/
public class BrewToolProcessor extends AbstractRepositoryToolProcessor<Brew> {
private static final String KEY_DISTRIBUTION_CHECKSUM_SHA_256 = "distributionChecksumSha256";
private static final String TPL_MAC_INTEL = " if OS.mac?\n" +
" url \"{{distributionUrl}}\"\n" +
" sha256 \"{{distributionChecksumSha256}}\"\n" +
" end\n";
private static final String TPL_LINUX_INTEL = " if OS.linux? && Hardware::CPU.intel?\n" +
" url \"{{distributionUrl}}\"\n" +
" sha256 \"{{distributionChecksumSha256}}\"\n" +
" end\n";
private static final String TPL_LINUX_ARM = " if OS.linux? && Hardware::CPU.arm?\n" +
" url \"{{distributionUrl}}\"\n" +
" sha256 \"{{distributionChecksumSha256}}\"\n" +
" end";
public BrewToolProcessor(JReleaserContext context) {
super(context);
}
@@ -98,16 +118,43 @@ public class BrewToolProcessor extends AbstractRepositoryToolProcessor<Brew> {
for (Artifact artifact : distribution.getArtifacts()) {
if (!artifact.isActive()) continue;
if (artifact.getPath().endsWith(".zip") && !isTrue(artifact.getExtraProperties().get("skipBrew"))) {
String artifactFileName = artifact.getEffectivePath(context).getFileName().toString();
Map<String, Object> newProps = new LinkedHashMap<>(props);
newProps.put(Constants.KEY_ARTIFACT_FILE_NAME, artifactFileName);
props.put(Constants.KEY_DISTRIBUTION_ARTIFACT_NAME, getFilename(artifactFileName));
String artifactUrl = applyTemplate(context.getModel().getRelease().getGitService().getDownloadUrl(), newProps);
props.put(Constants.KEY_DISTRIBUTION_URL, artifactUrl);
props.put(Constants.KEY_DISTRIBUTION_URL, resolveArtifactUrl(props, artifact));
props.put(Constants.KEY_BREW_CASK_HAS_BINARY, true);
break;
}
}
} else if (tool.isMultiPlatform()) {
List<String> multiPlatforms = new ArrayList<>();
for (Artifact artifact : distribution.getArtifacts()) {
if (!artifact.isActive() ||
!artifact.getPath().endsWith(".zip") ||
isBlank(artifact.getPlatform()) ||
isTrue(artifact.getExtraProperties().get("skipBrew"))) continue;
String template = null;
String artifactUrl = resolveArtifactUrl(props, artifact);
if (PlatformUtils.isMac(artifact.getPlatform())) {
template = TPL_MAC_INTEL;
} else if (PlatformUtils.isLinux(artifact.getPlatform())) {
if (artifact.getPlatform().contains("arm")) {
template = TPL_LINUX_ARM;
} else {
template = TPL_LINUX_INTEL;
}
}
if (isNotBlank(template)) {
Map<String, Object> newProps = new LinkedHashMap<>(props);
newProps.put(Constants.KEY_DISTRIBUTION_URL, artifactUrl);
newProps.put(KEY_DISTRIBUTION_CHECKSUM_SHA_256, artifact.getHash(Algorithm.SHA_256));
multiPlatforms.add(applyTemplate(template, newProps));
}
}
if (multiPlatforms.isEmpty()) {
throw new ToolProcessingException("There are no matching multi-platform binaries.");
}
props.put(Constants.KEY_BREW_MULTIPLATFORM, passThrough(String.join(System.lineSeparator()+" ", multiPlatforms)));
} else if ((distribution.getType() == Distribution.DistributionType.JAVA_BINARY ||
distribution.getType() == Distribution.DistributionType.SINGLE_JAR) &&
!isTrue(tool.getExtraProperties().get("javaSkip")) &&
@@ -122,6 +169,14 @@ public class BrewToolProcessor extends AbstractRepositoryToolProcessor<Brew> {
.collect(Collectors.toList()));
}
private String resolveArtifactUrl(Map<String, Object> props, Artifact artifact) {
String artifactFileName = artifact.getEffectivePath(context).getFileName().toString();
Map<String, Object> newProps = new LinkedHashMap<>(props);
newProps.put(Constants.KEY_ARTIFACT_FILE_NAME, artifactFileName);
newProps.put(Constants.KEY_DISTRIBUTION_ARTIFACT_NAME, getFilename(artifactFileName));
return applyTemplate(context.getModel().getRelease().getGitService().getDownloadUrl(), newProps);
}
@Override
protected void writeFile(Project project,
Distribution distribution,
@@ -133,13 +188,19 @@ public class BrewToolProcessor extends AbstractRepositoryToolProcessor<Brew> {
fileName = trimTplExtension(fileName);
if (tool.getCask().isEnabled()) {
if ("formula.rb".equals(fileName)) return;
if ("formula.rb".equals(fileName) || "formula-multi.rb".equals(fileName)) return;
Path outputFile = "cask.rb".equals(fileName) ?
outputDirectory.resolve("Casks").resolve(tool.getCask().getResolvedCaskName(props).concat(".rb")) :
outputDirectory.resolve(fileName);
writeFile(content, outputFile);
} else if (tool.isMultiPlatform()) {
if ("cask.rb".equals(fileName) || "formula.rb".equals(fileName)) return;
Path outputFile = "formula-multi.rb".equals(fileName) ?
outputDirectory.resolve("Formula").resolve(distribution.getExecutable().concat(".rb")) :
outputDirectory.resolve(fileName);
writeFile(content, outputFile);
} else {
if ("cask.rb".equals(fileName)) return;
if ("cask.rb".equals(fileName) || "formula-multi.rb".equals(fileName)) return;
Path outputFile = "formula.rb".equals(fileName) ?
outputDirectory.resolve("Formula").resolve(distribution.getExecutable().concat(".rb")) :
outputDirectory.resolve(fileName);

View File

@@ -137,6 +137,7 @@ public interface Constants {
String KEY_BREW_CASK_HAS_APPCAST = "brewCaskHasAppcast";
String KEY_BREW_CASK_APPCAST = "brewCaskAppcast";
String KEY_BREW_CASK_HAS_BINARY = "brewCaskHasBinary";
String KEY_BREW_MULTIPLATFORM = "brewMultiPlatform";
// Docker
String KEY_DOCKER_SPEC_NAME = "dockerSpecName";

View File

@@ -32,6 +32,8 @@ import org.gradle.api.provider.Property
interface Brew extends RepositoryTool {
Property<String> getFormulaName()
Property<Boolean> getMultiPlatform()
ListProperty<String> getLivecheck()
MapProperty<String, String> getDependencies()

View File

@@ -43,6 +43,7 @@ import static org.jreleaser.util.StringUtils.isNotBlank
@CompileStatic
class BrewImpl extends AbstractRepositoryTool implements Brew {
final Property<String> formulaName
final Property<Boolean> multiPlatform
final CommitAuthorImpl commitAuthor
final TapImpl tap
final CaskImpl cask
@@ -53,6 +54,7 @@ class BrewImpl extends AbstractRepositoryTool implements Brew {
BrewImpl(ObjectFactory objects) {
super(objects)
formulaName = objects.property(String).convention(Providers.notDefined())
multiPlatform = objects.property(Boolean).convention(Providers.notDefined())
tap = objects.newInstance(TapImpl, objects)
cask = objects.newInstance(CaskImpl, objects)
commitAuthor = objects.newInstance(CommitAuthorImpl, objects)
@@ -79,6 +81,7 @@ class BrewImpl extends AbstractRepositoryTool implements Brew {
boolean isSet() {
super.isSet() ||
formulaName.present ||
multiPlatform.present ||
dependencies.present ||
tap.isSet() ||
commitAuthor.isSet() ||
@@ -120,6 +123,7 @@ class BrewImpl extends AbstractRepositoryTool implements Brew {
org.jreleaser.model.Brew tool = new org.jreleaser.model.Brew()
fillToolProperties(tool)
if (formulaName.present) tool.formulaName = formulaName.get()
if (multiPlatform.present) tool.multiPlatform = multiPlatform.get()
if (tap.isSet()) tool.tap = tap.toHomebrewTap()
if (commitAuthor.isSet()) tool.commitAuthor = commitAuthor.toModel()
if (dependencies.present) tool.dependencies = dependencies.get()

View File

@@ -34,10 +34,12 @@ public class Brew extends AbstractRepositoryTool {
private final Cask cask = new Cask();
private String formulaName;
private Boolean multiPlatform;
void setAll(Brew brew) {
super.setAll(brew);
this.formulaName = brew.formulaName;
this.multiPlatform = brew.multiPlatform;
setTap(brew.tap);
setDependencies(brew.dependencies);
setLivecheck(brew.livecheck);
@@ -52,6 +54,18 @@ public class Brew extends AbstractRepositoryTool {
this.formulaName = formulaName;
}
public boolean isMultiPlatform() {
return multiPlatform != null && multiPlatform;
}
public void setMultiPlatform(Boolean multiPlatform) {
this.multiPlatform = multiPlatform;
}
public boolean isMultiPlatformSet() {
return multiPlatform != null;
}
public Tap getTap() {
return tap;
}
@@ -92,6 +106,7 @@ public class Brew extends AbstractRepositoryTool {
!dependencies.isEmpty() ||
tap.isSet() ||
!livecheck.isEmpty() ||
multiPlatform != null ||
cask.isSet();
}
}

View File

@@ -36,7 +36,7 @@ public class Cask {
private String pkgName;
private String appName;
private String appcast;
private boolean enabled;
private Boolean enabled;
void setAll(Cask cask) {
this.name = cask.name;
@@ -90,7 +90,11 @@ public class Cask {
}
public boolean isEnabled() {
return enabled;
return enabled != null && enabled;
}
public boolean isEnabledSet() {
return enabled != null;
}
public void setEnabled(boolean enabled) {

View File

@@ -771,6 +771,7 @@ public final class JReleaserModelConverter {
t.setExtraProperties(tool.getExtraProperties());
t.setTap(convertHomebrewTap(tool.getTap()));
t.setFormulaName(tool.getFormulaName());
if (tool.isMultiPlatformSet()) t.setMultiPlatform(tool.isMultiPlatform());
t.setCommitAuthor(convertCommitAuthor(tool.getCommitAuthor()));
tool.getDependencies().forEach(dependency -> {
if (isNotBlank(dependency.getValue())) {
@@ -793,7 +794,7 @@ public final class JReleaserModelConverter {
c.setPkgName(cask.getPkgName());
c.setAppName(cask.getAppName());
c.setAppcast(cask.getAppcast());
c.setEnabled(cask.isEnabled());
if (cask.isEnabledSet()) c.setEnabled(cask.isEnabled());
c.setUninstall(cask.getUninstall());
c.setZap(cask.getZap());
return c;