001/* 002 * JDrupes Builder 003 * Copyright (C) 2026 Michael N. Lipp 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <https://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.builder.distribution.internal; 020 021import com.google.common.flogger.FluentLogger; 022import java.io.ByteArrayOutputStream; 023import java.io.IOException; 024import java.nio.file.Files; 025import java.nio.file.Path; 026import java.util.ArrayList; 027import java.util.List; 028import org.apache.commons.compress.archivers.tar.TarArchiveEntry; 029import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; 030import org.jdrupes.builder.api.BuildException; 031import org.jdrupes.builder.api.FileResource; 032import org.jdrupes.builder.api.FileTree; 033import static org.jdrupes.builder.api.ResourceType.BaseFileTreeType; 034import org.jdrupes.builder.api.Resources; 035import org.jdrupes.builder.distribution.ApplicationTarFile; 036import org.jdrupes.builder.java.ClasspathElement; 037import static org.jdrupes.builder.java.JavaTypes.JarFileType; 038 039/// A distribution builder that builds a TAR file. 040/// 041public class TarDistributionBuilder extends DistributionBuilder { 042 private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 043 044 /// Initializes a new tar distribution builder. 045 /// 046 public TarDistributionBuilder() { 047 // Make javadoc happy 048 } 049 050 /// Builds the tar. 051 /// 052 /// @param file the file 053 /// @param config the config 054 /// @param cpElements the cp elements 055 /// 056 @SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops", "PMD.NcssCount", 057 "PMD.AvoidUsingOctalValues" }) 058 public void build(ApplicationTarFile file, 059 ApplicationConfigurationData config, 060 Resources<ClasspathElement> cpElements) { 061 try (var tos = new TarArchiveOutputStream( 062 Files.newOutputStream(file.path()))) { 063 List<String> distClassPath = new ArrayList<>(); 064 int classTreeCount = 0; 065 for (var cpe : cpElements.get()) { 066 if (JarFileType.isAssignableFrom(cpe.type())) { 067 var entryName = Path.of("lib").resolve( 068 cpe.toPath().getFileName()).toString(); 069 distClassPath.add("$APP_HOME/" + entryName); 070 tos.putArchiveEntry( 071 new TarArchiveEntry(cpe.toPath(), entryName)); 072 Files.copy(cpe.toPath(), tos); 073 tos.closeArchiveEntry(); 074 continue; 075 } 076 if (BaseFileTreeType.isAssignableFrom(cpe.type())) { 077 @SuppressWarnings("unchecked") 078 FileTree<FileResource> fileTree 079 = (FileTree<FileResource>) cpe; 080 if (fileTree.isEmpty()) { 081 continue; 082 } 083 classTreeCount += 1; 084 addClassTree(tos, distClassPath, classTreeCount, fileTree); 085 continue; 086 } 087 logger.atWarning().log("Cannot add %s to distribution", cpe); 088 } 089 090 // Add generated scripts, first Unix 091 var model = buildModel(config, distClassPath); 092 var bos = new ByteArrayOutputStream(); 093 addUnixScript(bos, model); 094 byte[] data = bos.toByteArray(); 095 var entry = new TarArchiveEntry( 096 Path.of("bin", config.executableName()).toString()); 097 entry.setSize(data.length); 098 entry.setMode(0755); 099 tos.putArchiveEntry(entry); 100 tos.write(data); 101 tos.closeArchiveEntry(); 102 103 // Now Windows 104 bos.reset(); 105 addWindowsBat(bos, model); 106 data = bos.toByteArray(); 107 entry = new TarArchiveEntry(Path.of("bin", 108 config.executableName() + ".bat").toString()); 109 entry.setSize(data.length); 110 entry.setMode(0755); 111 tos.putArchiveEntry(entry); 112 tos.write(data); 113 tos.closeArchiveEntry(); 114 } catch (IOException e) { 115 throw new BuildException().cause(e); 116 } 117 } 118 119 @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") 120 private void addClassTree(TarArchiveOutputStream tos, 121 List<String> distClassPath, int classTreeCount, 122 FileTree<FileResource> tree) throws IOException { 123 var treeDir = Path.of("classes") 124 .resolve(Integer.toString(classTreeCount)); 125 distClassPath.add("$APP_HOME/" + treeDir.toString()); 126 var iter = tree.entries().iterator(); 127 while (iter.hasNext()) { 128 var entry = iter.next(); 129 tos.putArchiveEntry( 130 new TarArchiveEntry(tree.root().resolve(entry.path()), 131 treeDir.resolve(entry.path()).toString())); 132 Files.copy(tree.root().resolve(entry.path()), tos); 133 tos.closeArchiveEntry(); 134 } 135 } 136 137}