1 /* |
|
2 * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 |
|
26 package jdk.jpackage.internal; |
|
27 |
|
28 import java.io.*; |
|
29 import java.nio.file.Path; |
|
30 import java.text.MessageFormat; |
|
31 import java.util.*; |
|
32 import java.util.regex.Matcher; |
|
33 import java.util.regex.Pattern; |
|
34 import java.util.stream.Collectors; |
|
35 import java.util.stream.Stream; |
|
36 |
|
37 import static jdk.jpackage.internal.StandardBundlerParam.*; |
|
38 import static jdk.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR; |
|
39 import static jdk.jpackage.internal.OverridableResource.createResource; |
|
40 |
|
41 /** |
|
42 * There are two command line options to configure license information for RPM |
|
43 * packaging: --linux-rpm-license-type and --license-file. Value of |
|
44 * --linux-rpm-license-type command line option configures "License:" section |
|
45 * of RPM spec. Value of --license-file command line option specifies a license |
|
46 * file to be added to the package. License file is a sort of documentation file |
|
47 * but it will be installed even if user selects an option to install the |
|
48 * package without documentation. --linux-rpm-license-type is the primary option |
|
49 * to set license information. --license-file makes little sense in case of RPM |
|
50 * packaging. |
|
51 */ |
|
52 public class LinuxRpmBundler extends LinuxPackageBundler { |
|
53 |
|
54 // Fedora rules for package naming are used here |
|
55 // https://fedoraproject.org/wiki/Packaging:NamingGuidelines?rd=Packaging/NamingGuidelines |
|
56 // |
|
57 // all Fedora packages must be named using only the following ASCII |
|
58 // characters. These characters are displayed here: |
|
59 // |
|
60 // abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._+ |
|
61 // |
|
62 private static final Pattern RPM_PACKAGE_NAME_PATTERN = |
|
63 Pattern.compile("[a-z\\d\\+\\-\\.\\_]+", Pattern.CASE_INSENSITIVE); |
|
64 |
|
65 public static final BundlerParamInfo<String> PACKAGE_NAME = |
|
66 new StandardBundlerParam<> ( |
|
67 Arguments.CLIOptions.LINUX_BUNDLE_NAME.getId(), |
|
68 String.class, |
|
69 params -> { |
|
70 String nm = APP_NAME.fetchFrom(params); |
|
71 if (nm == null) return null; |
|
72 |
|
73 // make sure to lower case and spaces become dashes |
|
74 nm = nm.toLowerCase().replaceAll("[ ]", "-"); |
|
75 |
|
76 return nm; |
|
77 }, |
|
78 (s, p) -> { |
|
79 if (!RPM_PACKAGE_NAME_PATTERN.matcher(s).matches()) { |
|
80 String msgKey = "error.invalid-value-for-package-name"; |
|
81 throw new IllegalArgumentException( |
|
82 new ConfigException(MessageFormat.format( |
|
83 I18N.getString(msgKey), s), |
|
84 I18N.getString(msgKey + ".advice"))); |
|
85 } |
|
86 |
|
87 return s; |
|
88 } |
|
89 ); |
|
90 |
|
91 public static final BundlerParamInfo<String> LICENSE_TYPE = |
|
92 new StandardBundlerParam<>( |
|
93 Arguments.CLIOptions.LINUX_RPM_LICENSE_TYPE.getId(), |
|
94 String.class, |
|
95 params -> I18N.getString("param.license-type.default"), |
|
96 (s, p) -> s |
|
97 ); |
|
98 |
|
99 public static final BundlerParamInfo<String> GROUP = |
|
100 new StandardBundlerParam<>( |
|
101 Arguments.CLIOptions.LINUX_CATEGORY.getId(), |
|
102 String.class, |
|
103 params -> null, |
|
104 (s, p) -> s); |
|
105 |
|
106 private final static String DEFAULT_SPEC_TEMPLATE = "template.spec"; |
|
107 |
|
108 public final static String TOOL_RPM = "rpm"; |
|
109 public final static String TOOL_RPMBUILD = "rpmbuild"; |
|
110 public final static DottedVersion TOOL_RPMBUILD_MIN_VERSION = DottedVersion.lazy( |
|
111 "4.0"); |
|
112 |
|
113 public LinuxRpmBundler() { |
|
114 super(PACKAGE_NAME); |
|
115 } |
|
116 |
|
117 @Override |
|
118 public void doValidate(Map<String, ? super Object> params) |
|
119 throws ConfigException { |
|
120 } |
|
121 |
|
122 private static ToolValidator createRpmbuildToolValidator() { |
|
123 Pattern pattern = Pattern.compile(" (\\d+\\.\\d+)"); |
|
124 return new ToolValidator(TOOL_RPMBUILD).setMinimalVersion( |
|
125 TOOL_RPMBUILD_MIN_VERSION).setVersionParser(lines -> { |
|
126 String versionString = lines.limit(1).collect( |
|
127 Collectors.toList()).get(0); |
|
128 Matcher matcher = pattern.matcher(versionString); |
|
129 if (matcher.find()) { |
|
130 return matcher.group(1); |
|
131 } |
|
132 return null; |
|
133 }); |
|
134 } |
|
135 |
|
136 @Override |
|
137 protected List<ToolValidator> getToolValidators( |
|
138 Map<String, ? super Object> params) { |
|
139 return List.of(createRpmbuildToolValidator()); |
|
140 } |
|
141 |
|
142 @Override |
|
143 protected File buildPackageBundle( |
|
144 Map<String, String> replacementData, |
|
145 Map<String, ? super Object> params, File outputParentDir) throws |
|
146 PackagerException, IOException { |
|
147 |
|
148 Path specFile = specFile(params); |
|
149 |
|
150 // prepare spec file |
|
151 createResource(DEFAULT_SPEC_TEMPLATE, params) |
|
152 .setCategory(I18N.getString("resource.rpm-spec-file")) |
|
153 .setSubstitutionData(replacementData) |
|
154 .saveToFile(specFile); |
|
155 |
|
156 return buildRPM(params, outputParentDir.toPath()).toFile(); |
|
157 } |
|
158 |
|
159 @Override |
|
160 protected Map<String, String> createReplacementData( |
|
161 Map<String, ? super Object> params) throws IOException { |
|
162 Map<String, String> data = new HashMap<>(); |
|
163 |
|
164 data.put("APPLICATION_DIRECTORY", Path.of(LINUX_INSTALL_DIR.fetchFrom( |
|
165 params), PACKAGE_NAME.fetchFrom(params)).toString()); |
|
166 data.put("APPLICATION_SUMMARY", APP_NAME.fetchFrom(params)); |
|
167 data.put("APPLICATION_LICENSE_TYPE", LICENSE_TYPE.fetchFrom(params)); |
|
168 |
|
169 String licenseFile = LICENSE_FILE.fetchFrom(params); |
|
170 if (licenseFile != null) { |
|
171 licenseFile = Path.of(licenseFile).toAbsolutePath().normalize().toString(); |
|
172 } |
|
173 data.put("APPLICATION_LICENSE_FILE", licenseFile); |
|
174 data.put("APPLICATION_GROUP", GROUP.fetchFrom(params)); |
|
175 |
|
176 return data; |
|
177 } |
|
178 |
|
179 @Override |
|
180 protected void initLibProvidersLookup( |
|
181 Map<String, ? super Object> params, |
|
182 LibProvidersLookup libProvidersLookup) { |
|
183 libProvidersLookup.setPackageLookup(file -> { |
|
184 return Executor.of(TOOL_RPM, |
|
185 "-q", "--queryformat", "%{name}\\n", |
|
186 "-q", "--whatprovides", file.toString()) |
|
187 .saveOutput(true).executeExpectSuccess().getOutput().stream(); |
|
188 }); |
|
189 } |
|
190 |
|
191 @Override |
|
192 protected List<ConfigException> verifyOutputBundle( |
|
193 Map<String, ? super Object> params, Path packageBundle) { |
|
194 List<ConfigException> errors = new ArrayList<>(); |
|
195 |
|
196 String specFileName = specFile(params).getFileName().toString(); |
|
197 |
|
198 try { |
|
199 List<PackageProperty> properties = List.of( |
|
200 new PackageProperty("Name", PACKAGE_NAME.fetchFrom(params), |
|
201 "APPLICATION_PACKAGE", specFileName), |
|
202 new PackageProperty("Version", VERSION.fetchFrom(params), |
|
203 "APPLICATION_VERSION", specFileName), |
|
204 new PackageProperty("Release", RELEASE.fetchFrom(params), |
|
205 "APPLICATION_RELEASE", specFileName), |
|
206 new PackageProperty("Arch", rpmArch(), null, specFileName)); |
|
207 |
|
208 List<String> actualValues = Executor.of(TOOL_RPM, "-qp", "--queryformat", |
|
209 properties.stream().map(entry -> String.format("%%{%s}", |
|
210 entry.name)).collect(Collectors.joining("\\n")), |
|
211 packageBundle.toString()).saveOutput(true).executeExpectSuccess().getOutput(); |
|
212 |
|
213 Iterator<String> actualValuesIt = actualValues.iterator(); |
|
214 properties.forEach(property -> errors.add(property.verifyValue( |
|
215 actualValuesIt.next()))); |
|
216 } catch (IOException ex) { |
|
217 // Ignore error as it is not critical. Just report it. |
|
218 Log.verbose(ex); |
|
219 } |
|
220 |
|
221 return errors; |
|
222 } |
|
223 |
|
224 /** |
|
225 * Various ways to get rpm arch. Needed to address JDK-8233143. rpmbuild is |
|
226 * mandatory for rpm packaging, try it first. rpm is optional and may not be |
|
227 * available, use as the last resort. |
|
228 */ |
|
229 private enum RpmArchReader { |
|
230 Rpmbuild(TOOL_RPMBUILD, "--eval=%{_target_cpu}"), |
|
231 Rpm(TOOL_RPM, "--eval=%{_target_cpu}"); |
|
232 |
|
233 RpmArchReader(String... cmdline) { |
|
234 this.cmdline = cmdline; |
|
235 } |
|
236 |
|
237 String getRpmArch() throws IOException { |
|
238 Executor exec = Executor.of(cmdline).saveOutput(true); |
|
239 if (this == values()[values().length - 1]) { |
|
240 exec.executeExpectSuccess(); |
|
241 } else if (exec.execute() != 0) { |
|
242 return null; |
|
243 } |
|
244 |
|
245 return exec.getOutput().get(0); |
|
246 } |
|
247 |
|
248 private final String[] cmdline; |
|
249 } |
|
250 |
|
251 private String rpmArch() throws IOException { |
|
252 if (rpmArch == null) { |
|
253 for (var rpmArchReader : RpmArchReader.values()) { |
|
254 rpmArch = rpmArchReader.getRpmArch(); |
|
255 if (rpmArch != null) { |
|
256 break; |
|
257 } |
|
258 } |
|
259 } |
|
260 return rpmArch; |
|
261 } |
|
262 |
|
263 private Path specFile(Map<String, ? super Object> params) { |
|
264 return TEMP_ROOT.fetchFrom(params).toPath().resolve(Path.of("SPECS", |
|
265 PACKAGE_NAME.fetchFrom(params) + ".spec")); |
|
266 } |
|
267 |
|
268 private Path buildRPM(Map<String, ? super Object> params, |
|
269 Path outdir) throws IOException { |
|
270 |
|
271 Path rpmFile = outdir.toAbsolutePath().resolve(String.format( |
|
272 "%s-%s-%s.%s.rpm", PACKAGE_NAME.fetchFrom(params), |
|
273 VERSION.fetchFrom(params), RELEASE.fetchFrom(params), rpmArch())); |
|
274 |
|
275 Log.verbose(MessageFormat.format(I18N.getString( |
|
276 "message.outputting-bundle-location"), |
|
277 rpmFile.getParent())); |
|
278 |
|
279 PlatformPackage thePackage = createMetaPackage(params); |
|
280 |
|
281 //run rpmbuild |
|
282 Executor.of( |
|
283 TOOL_RPMBUILD, |
|
284 "-bb", specFile(params).toAbsolutePath().toString(), |
|
285 "--define", String.format("%%_sourcedir %s", |
|
286 thePackage.sourceRoot()), |
|
287 // save result to output dir |
|
288 "--define", String.format("%%_rpmdir %s", rpmFile.getParent()), |
|
289 // do not use other system directories to build as current user |
|
290 "--define", String.format("%%_topdir %s", |
|
291 TEMP_ROOT.fetchFrom(params).toPath().toAbsolutePath()), |
|
292 "--define", String.format("%%_rpmfilename %s", rpmFile.getFileName()) |
|
293 ).executeExpectSuccess(); |
|
294 |
|
295 Log.verbose(MessageFormat.format( |
|
296 I18N.getString("message.output-bundle-location"), |
|
297 rpmFile.getParent())); |
|
298 |
|
299 return rpmFile; |
|
300 } |
|
301 |
|
302 @Override |
|
303 public String getName() { |
|
304 return I18N.getString("rpm.bundler.name"); |
|
305 } |
|
306 |
|
307 @Override |
|
308 public String getID() { |
|
309 return "rpm"; |
|
310 } |
|
311 |
|
312 @Override |
|
313 public boolean supported(boolean runtimeInstaller) { |
|
314 return Platform.isLinux() && (createRpmbuildToolValidator().validate() == null); |
|
315 } |
|
316 |
|
317 @Override |
|
318 public boolean isDefault() { |
|
319 return !LinuxDebBundler.isDebian(); |
|
320 } |
|
321 |
|
322 private String rpmArch; |
|
323 } |
|