/* * Copyright 2015 - 2016 i-net software * * 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 * * http://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 com.inet.gradle.setup.unix.rpm; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Scanner; import com.inet.gradle.setup.SetupBuilder; import com.inet.gradle.setup.abstracts.DesktopStarter; import com.inet.gradle.setup.abstracts.LocalizedResource; /** * Builder for the SPEC file, that is required for the Redhat package tool. * <br> * This file contains settings for the package like dependencies, architecture, description and * scripts and commands that are executed before, during and after the installation. * * @author Stefan Heidrich */ class RpmControlFileBuilder { private static final char NEWLINE = '\n'; private final Rpm rpm; private final SetupBuilder setup; private File buildDir; private Collection<String> confFiles = new ArrayList<>(); enum Script { PREINSTHEAD, PREINSTTAIL, POSTINSTHEAD, POSTINSTTAIL, PRERMHEAD, PRERMTAIL, POSTRMHEAD, POSTRMTAIL } Map<Script, StringBuilder> scriptMap = new HashMap<>(); /** * the constructor setting the fields * * @param rpm the task for the redhat package * @param setup the generic task for all setups * @param buildDir the directory to build the package in */ RpmControlFileBuilder( Rpm rpm, SetupBuilder setup, File buildDir ) { this.rpm = rpm; this.setup = setup; this.buildDir = buildDir; } /** * Create the configuration files for the RedHat package based on the settings in the task. * * @throws Exception */ void build() throws Exception { createControlFile(); } /** * Creates the SPEC file for the package * * @throws IOException if something could not be written to the file */ private void createControlFile() throws IOException { if( !buildDir.exists() ) { buildDir.mkdirs(); } else if( !buildDir.isDirectory() ) { throw new IllegalArgumentException( "The buildDir parameter must be a directory!" ); } FileOutputStream fileoutput = null; OutputStreamWriter controlWriter = null; try { File spec = new File( buildDir, setup.getAppIdentifier() + ".spec" ); fileoutput = new FileOutputStream( spec ); controlWriter = new OutputStreamWriter( fileoutput, "UTF-8" ); putSummary( controlWriter ); putName( controlWriter ); putVersion( controlWriter ); putRelease( controlWriter ); putLicense( controlWriter ); putSection( controlWriter ); putBuildRoot( controlWriter ); putURL( controlWriter ); putVendor( controlWriter ); putPackager( controlWriter ); putPrefix( controlWriter ); putDepends( controlWriter ); putArchitecture( controlWriter ); putBackwardCompatibility( controlWriter ); putDescription( controlWriter ); putPrep( controlWriter ); putBuild( controlWriter ); putInstall( controlWriter ); putClean( controlWriter ); putFiles( controlWriter ); putPre( controlWriter ); putPost( controlWriter ); putPreun( controlWriter ); putPostun( controlWriter ); controlWriter.flush(); } finally { if( controlWriter != null ) { try { controlWriter.close(); } catch( IOException e ) { // IGNORE } } if( fileoutput != null ) { try { fileoutput.close(); } catch( IOException e ) { // IGNORE } } } } /** * Insert some defines for backward compatibility with old rpm versions. * This could be enabled and disabled with the backwardCompatibility entry of the RPM task. * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putBackwardCompatibility( OutputStreamWriter controlWriter ) throws IOException { if( rpm.isBackwardCompatible() ) { controlWriter.write( NEWLINE + "%define _binary_payload w9.gzdio" + NEWLINE ); controlWriter.write( NEWLINE + "%define _source_payload w9.gzdio" + NEWLINE ); controlWriter.write( NEWLINE + "%define _binary_filedigest_algorithm 1" + NEWLINE ); controlWriter.write( NEWLINE + "%define _source_filedigest_algorithm 1" + NEWLINE ); } } /** * Get executed before the package has been removed * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putPreun( OutputStreamWriter controlWriter ) throws IOException { controlWriter.write( NEWLINE + "%preun" + NEWLINE ); controlWriter.write( NEWLINE + "if [ $1 -eq 0 ]; then" + NEWLINE ); controlWriter.write( NEWLINE + "echo \"preun step\"" + NEWLINE ); //Set some variables to begin with controlWriter.write( rpm.getVariablesTemplate() + NEWLINE ); // Write the content writeHeadContentTail( controlWriter, Script.PRERMHEAD, rpm.getPrerm(), Script.PRERMTAIL ); // removes only the files in the installation path List<String> del_files = setup.getDeleteFiles(); for( String file : del_files ) { controlWriter.write( "if [ -f \"${RPM_INSTALL_PREFIX}/" + file + "\" ]; then\n rm -f \"${RPM_INSTALL_PREFIX}/" + file + "\"\nfi" + NEWLINE ); } // removes only the dirs in the installation path List<String> del_dirs = setup.getDeleteFolders(); for( String dirs : del_dirs ) { controlWriter.write( "rm -R -f \"${RPM_INSTALL_PREFIX}/" + dirs + "\"" + NEWLINE ); } DesktopStarter starter = setup.getRunBeforeUninstall(); if( starter != null ) { controlWriter.write( NEWLINE ); String executable = starter.getExecutable(); String mainClass = starter.getMainClass(); String workingDir = starter.getWorkDir() != null ? "/" + starter.getWorkDir() : ""; if( executable != null ) { if( rpm.getDaemonUser().equalsIgnoreCase( "root" ) ) { controlWriter.write( "( cd \"${RPM_INSTALL_PREFIX}" + workingDir + "\" && " + executable + " " + starter.getStartArguments() + " )" + NEWLINE ); } else { controlWriter.write( "(su " + rpm.getDaemonUser() + " -c 'cd \"${RPM_INSTALL_PREFIX}" + workingDir + "\" && " + executable + " " + starter.getStartArguments() + "' )" + NEWLINE ); } } else if( mainClass != null ) { if( rpm.getDaemonUser().equalsIgnoreCase( "root" ) ) { controlWriter.write( "( cd \"${RPM_INSTALL_PREFIX}" + workingDir + "\" && java -cp \"" + starter.getMainJar() + "\" " + mainClass + " " + starter.getStartArguments() + " )" + NEWLINE ); } else { controlWriter.write( "(su " + rpm.getDaemonUser() + " -c 'cd \"${RPM_INSTALL_PREFIX}/" + workingDir + "\" && java -cp \"" + starter.getMainJar() + "\" " + mainClass + " " + starter.getStartArguments() + "' )" + NEWLINE ); } } controlWriter.write( NEWLINE ); } controlWriter.write( NEWLINE + "fi" + NEWLINE ); } /** * This script is executed before the package has been installed. * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putPre( OutputStreamWriter controlWriter ) throws IOException { controlWriter.write( NEWLINE + "%pre" + NEWLINE ); //Set some variables to begin with controlWriter.write( rpm.getVariablesTemplate() + NEWLINE ); // Write the content writeHeadContentTail( controlWriter, Script.PREINSTHEAD, rpm.getPreinst(), Script.PREINSTTAIL ); // removes only the files in the installation path List<String> del_files = setup.getDeleteFiles(); for( String file : del_files ) { controlWriter.write( "if [ -f \"${RPM_INSTALL_PREFIX}/" + file + "\" ]; then\n rm -f \"${RPM_INSTALL_PREFIX}/" + file + "\"\nfi" + NEWLINE ); } // removes only the dirs in the installation path List<String> del_dirs = setup.getDeleteFolders(); for( String dirs : del_dirs ) { controlWriter.write( "rm -R -f \"${RPM_INSTALL_PREFIX}/" + dirs + "\"" + NEWLINE ); } } /** * This script is executed after the package has been installed. * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putPost( OutputStreamWriter controlWriter ) throws IOException { controlWriter.write( NEWLINE + "%post" + NEWLINE ); //Set some variables to begin with controlWriter.write( rpm.getVariablesTemplate() + NEWLINE ); // Write the content writeHeadContentTail( controlWriter, Script.POSTINSTHEAD, rpm.getPostinst(), Script.POSTINSTTAIL ); DesktopStarter runAfterStarter = setup.getRunAfter(); if( runAfterStarter != null ) { String executable = runAfterStarter.getExecutable(); String mainClass = runAfterStarter.getMainClass(); String workingDir = runAfterStarter.getWorkDir() != null ? "/" + runAfterStarter.getWorkDir() : ""; if( executable != null ) { controlWriter.write( "( cd \"${RPM_INSTALL_PREFIX}" + workingDir + "\" && " + executable + " " + runAfterStarter.getStartArguments() + " & )" + NEWLINE ); } else if( mainClass != null ) { controlWriter.write( "( cd \"${RPM_INSTALL_PREFIX}" + workingDir + "\" && java -cp \"" + runAfterStarter.getMainJar() + "\" " + mainClass + " " + runAfterStarter.getStartArguments() + " )" + NEWLINE ); } } controlWriter.write( "gtk-update-icon-cache /usr/share/icons/hicolor &>/dev/null || :" + NEWLINE ); } /** * This script is executed after the package has been removed. * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putPostun( OutputStreamWriter controlWriter ) throws IOException { controlWriter.write( NEWLINE + "%postun" + NEWLINE ); if( rpm.getPostrm().size() > 0 || scriptMap.get( Script.POSTRMHEAD ) != null || scriptMap.get( Script.POSTRMTAIL ) != null ) { controlWriter.write( NEWLINE + "if [ $1 -eq 0 ]; then" + NEWLINE ); } //Set some variables to begin with controlWriter.write( rpm.getVariablesTemplate() + NEWLINE ); // Write the content writeHeadContentTail( controlWriter, Script.POSTRMHEAD, rpm.getPostrm(), Script.POSTRMTAIL ); if( rpm.getPostrm().size() > 0 || scriptMap.get( Script.POSTRMHEAD ) != null || scriptMap.get( Script.POSTRMTAIL ) != null ) { controlWriter.write( NEWLINE + "fi" + NEWLINE ); } } /** * Specifies the files that should be installed. The files will be installed under the installationRoot in the system. * If files need to be installed somewhere else these files need to be copied via the post step. * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putFiles( OutputStreamWriter controlWriter ) throws IOException { controlWriter.write( NEWLINE + "%files" + NEWLINE ); controlWriter.write( "\"" + rpm.getInstallationRoot() + "\"" + NEWLINE ); // nimmt anscheinend nicht die Files in der Root if( setup.getDesktopStarters() != null && setup.getDesktopStarters().size() > 0 ) { controlWriter.write( "/usr/share/applications/*" + NEWLINE ); controlWriter.write( "/usr/share/icons/**/*" + NEWLINE ); controlWriter.write( "/usr/bin/*" + NEWLINE ); } if( setup.getServices() != null && setup.getServices().size() > 0 ) { controlWriter.write( "/etc/init.d/*" + NEWLINE ); } if( rpm.getDefaultServiceFile() != null ) { controlWriter.write( "/etc/sysconfig/*" + NEWLINE ); } if( setup.getLicenseFiles() != null && setup.getLicenseFiles().size() > 0 ) { controlWriter.write( "/usr/share/licenses/**/*" + NEWLINE ); } } /** * This is used to clean up the build directory tree. Normally RPM does this for you. * During the clean step the created package will be copied to the distribution directory. * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putClean( OutputStreamWriter controlWriter ) throws IOException { controlWriter.write( NEWLINE + "%clean" + NEWLINE ); String release = rpm.getRelease(); if( release == null || release.length() == 0 ) { release = "1"; } controlWriter.write( "cp ../SRPMS/" + setup.getAppIdentifier() + "-" + setup.getVersion() + "-" + release + ".src.rpm '" + setup.getDestinationDir().getAbsolutePath() + "'" + NEWLINE ); controlWriter.write( "mv -f ../RPMS/" + rpm.getArchitecture() + "/" + setup.getAppIdentifier() + "-" + setup.getVersion() + "-" + release + "." + rpm.getArchitecture() + ".rpm '" + rpm.getSetupFile() + "'" + NEWLINE ); //Set some variables to begin with controlWriter.write( rpm.getVariablesTemplate() + NEWLINE ); ArrayList<String> cleans = rpm.getClean(); for( String clean : cleans ) { controlWriter.write( clean + NEWLINE ); } } /** * Contains the necessary steps to install the build software * The files in the BUILD directory needs to be copied into the BUILDROOT directory so that the install step * finds the files. * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putInstall( OutputStreamWriter controlWriter ) throws IOException { controlWriter.write( NEWLINE + "%install" + NEWLINE ); controlWriter.write( "cp -R . '%{buildroot}'" + NEWLINE ); // if(setup.getServices() != null && setup.getServices().size() > 0) { // controlWriter.write("cp -R etc %{buildroot}" + NEWLINE); // } //Set some variables to begin with controlWriter.write( rpm.getVariablesTemplate() + NEWLINE ); ArrayList<String> installs = rpm.getInstall(); for( String install : installs ) { controlWriter.write( install + NEWLINE ); } } /** * This will be executed during the building of the package * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putBuild( OutputStreamWriter controlWriter ) throws IOException { controlWriter.write( NEWLINE + "%build" + NEWLINE ); //Set some variables to begin with controlWriter.write( rpm.getVariablesTemplate() + NEWLINE ); ArrayList<String> builds = rpm.getBuild(); for( String build : builds ) { controlWriter.write( build + NEWLINE ); } } /** * This is the first script RPM executes during a build. * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putPrep( OutputStreamWriter controlWriter ) throws IOException { controlWriter.write( NEWLINE + "%prep" + NEWLINE ); //Set some variables to begin with controlWriter.write( rpm.getVariablesTemplate() + NEWLINE ); ArrayList<String> preps = rpm.getPrep(); for( String prep : preps ) { controlWriter.write( prep + NEWLINE ); } } /** * Write the prefix to the file. * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putPrefix( OutputStreamWriter controlWriter ) throws IOException { String prefix = rpm.getInstallationRoot(); controlWriter.write( "Prefix: \"" + prefix + "\"" + NEWLINE ); } /** * Write the packager to the file. The packager is the same as the vendor. * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putPackager( OutputStreamWriter controlWriter ) throws IOException { String vendor = setup.getVendor(); if( vendor == null || vendor.length() == 0 ) { throw new RuntimeException( "No vendor declared in the setup configuration." ); } else { controlWriter.write( "Packager: " + vendor + NEWLINE ); } } /** * Write the homepage url to the file. * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putURL( OutputStreamWriter controlWriter ) throws IOException { String url = rpm.getHomepage(); if( url != null && url.length() > 0 ) { controlWriter.write( "URL: " + url + NEWLINE ); } } /** * Write the BuildRoot entry to the file. * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putBuildRoot( OutputStreamWriter controlWriter ) throws IOException { controlWriter.write( "BuildRoot: ${_builddir}/%{name}-root" + NEWLINE ); } /** * Write the license to the file. If no license is specified 'Restricted' is used. * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putLicense( OutputStreamWriter controlWriter ) throws IOException { String license = rpm.getLicense(); if( license == null || license.length() == 0 ) { license = "Restricted"; } controlWriter.write( "License: " + license + NEWLINE ); } /** * Write the release to the file. If no release is specified '1' is used. * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putRelease( OutputStreamWriter controlWriter ) throws IOException { String release = rpm.getRelease(); if( release == null || release.length() == 0 ) { release = "1"; } controlWriter.write( "Release: " + release + NEWLINE ); } /** * Write the description to the file. The description is created from the long description entries. * The description for language specified with the defaultDescriptionLanguage property will be used as default description * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putDescription( OutputStreamWriter controlWriter ) throws IOException { controlWriter.write( NEWLINE + "%define __jar_repack %{nil}" + NEWLINE ); List<LocalizedResource> descriptions = setup.getLongDescriptions(); if( descriptions.size() > 0 ) { for( LocalizedResource desc : descriptions ) { String lang = desc.getLanguage(); String content = NEWLINE + "%description"; content += (lang.equalsIgnoreCase( setup.getDefaultResourceLanguage() ) ? "" + NEWLINE : " -l " + lang + NEWLINE); try (Scanner scanner = new Scanner( desc.getResource(), "UTF8" )) { content += scanner.useDelimiter( "\\A" ).next(); } finally { controlWriter.write( content + NEWLINE ); } } } else { controlWriter.write( NEWLINE + "%description" + NEWLINE + NEWLINE ); } } /** * Write the vendor to the file. The vendor is mandatory. If no vendor is declared a runtime exception will be thrown. * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putVendor( OutputStreamWriter controlWriter ) throws IOException { String vendor = setup.getVendor(); if( vendor == null || vendor.length() == 0 ) { throw new RuntimeException( "No vendor declared in the setup configuration." ); } else { controlWriter.write( "Vendor: " + vendor + NEWLINE ); } } /** * Write the dependencies to the file. * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putDepends( OutputStreamWriter controlWriter ) throws IOException { String depends = rpm.getDepends(); if( depends == null || depends.length() == 0 ) { depends = ""; } if( setup.getServices() != null && setup.getServices().size() > 0 ) { // depends = depends; // + ", daemonize, initscripts"; } if( depends.trim().length() > 0 ) { controlWriter.write( "Requires: " + depends + NEWLINE ); } } /** * Write the architecture to the file. If no architecture is specified then 'noarch' will be used. * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putArchitecture( OutputStreamWriter controlWriter ) throws IOException { controlWriter.write( "BuildArchitectures: " + rpm.getArchitecture() + NEWLINE ); } /** * Write the section to the file. * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putSection( OutputStreamWriter controlWriter ) throws IOException { String section = rpm.getSection(); if( section != null && section.length() > 0 ) { controlWriter.write( "Group: " + section + NEWLINE ); } } /** * Write the version to the file. The version is mandatory. If no version is declared a runtime exception will be thrown. * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putVersion( OutputStreamWriter controlWriter ) throws IOException { String version = setup.getVersion(); if( version == null || version.length() == 0 ) { throw new RuntimeException( "No version declared in the setup configuration." ); } else { controlWriter.write( "Version: " + version + NEWLINE ); } } /** * Write the summary to the file. The summary is mandatory. If no summary is declared a runtime exception will be thrown. * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putSummary( OutputStreamWriter controlWriter ) throws IOException { String summary = rpm.getSummary(); if( summary == null || summary.length() == 0 ) { throw new RuntimeException( "No summary declared in the setup configuration." ); } else { controlWriter.write( "Summary: " + summary + NEWLINE ); } } /** * Write the package to the file. The package is mandatory. If no package is declared a runtime exception will be thrown. * * @param controlWriter the writer for the file * @throws IOException if the was an error while writing to the file */ private void putName( OutputStreamWriter controlWriter ) throws IOException { String packages = setup.getAppIdentifier(); if( packages == null || packages.length() == 0 ) { throw new RuntimeException( "No package declared in the setup configuration." ); } else { controlWriter.write( "Name: " + packages + NEWLINE ); } } /** * Adds a config file * * @param file the config file */ public void addConfFile( String file ) { confFiles.add( file ); } /** * Adds a fragment to the install script at the specified section. * These sections are the pre, post, preun and the postun sections. * * @param script the install script section * @param scriptFragment the fragment to add */ public void addScriptFragment( Script script, String scriptFragment ) { StringBuilder sb = scriptMap.get( script ); if( sb == null ) { sb = new StringBuilder(); scriptMap.put( script, sb ); } else { sb.append( "\n\n" ); } sb.append( scriptFragment ); } /** * Writes the head, body and tail sections to the controlWriter * @param controlWriter the writer * @param headSection constant for the head * @param bodySection the list of strings for the body * @param tailSection constant for the tail * @throws IOException in case of errors */ private void writeHeadContentTail( OutputStreamWriter controlWriter, Script headSection, ArrayList<String> bodySection, Script tailSection ) throws IOException { StringBuilder head = scriptMap.get( headSection); if( head != null ) { controlWriter.write( head.toString() + NEWLINE ); } for( String body : bodySection ) { controlWriter.write( body + NEWLINE ); } StringBuilder tail = scriptMap.get( tailSection ); if( tail != null ) { controlWriter.write( tail.toString() + NEWLINE ); } } }