1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmCPackFreeBSDGenerator.h"
15 #include "cmArchiveWrite.h"
16 #include "cmCPackArchiveGenerator.h"
17 #include "cmCPackLog.h"
18 #include "cmGeneratedFileStream.h"
20 #include "cmStringAlgorithms.h"
21 #include "cmSystemTools.h"
22 #include "cmWorkingDirectory.h"
24 // Suffix used to tell libpkg what compression to use
25 static const char FreeBSDPackageCompression
[] = "txz";
26 static const char FreeBSDPackageSuffix_17
[] = ".pkg";
28 cmCPackFreeBSDGenerator::cmCPackFreeBSDGenerator()
29 : cmCPackArchiveGenerator(cmArchiveWrite::CompressXZ
, "paxr",
30 FreeBSDPackageSuffix_17
)
34 int cmCPackFreeBSDGenerator::InitializeInternal()
36 this->SetOptionIfNotSet("CPACK_PACKAGING_INSTALL_PREFIX", "/usr/local");
37 this->SetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY", "0");
38 return this->Superclass::InitializeInternal();
41 cmCPackFreeBSDGenerator::~cmCPackFreeBSDGenerator() = default;
43 // This is a wrapper for struct pkg_create and pkg_create()
45 // Instantiate this class with suitable parameters, then
46 // check isValid() to check if it's ok. Afterwards, call
47 // Create() to do the actual work. This will leave a package
48 // in the given `output_dir`.
50 // This wrapper cleans up the struct pkg_create.
58 PkgCreate(const std::string
& output_dir
, const std::string
& toplevel_dir
,
59 const std::string
& manifest_name
)
61 , manifest(manifest_name
)
65 pkg_create_set_format(d
, FreeBSDPackageCompression
);
66 pkg_create_set_compression_level(d
, 0); // Explicitly set default
67 pkg_create_set_overwrite(d
, false);
68 pkg_create_set_rootdir(d
, toplevel_dir
.c_str());
69 pkg_create_set_output_dir(d
, output_dir
.c_str());
78 bool isValid() const { return d
; }
84 // The API in the FreeBSD sources (the header has no documentation),
87 // int pkg_create(struct pkg_create *pc, const char *metadata, const char
90 // We let the plist be determined from what is installed, and all
91 // the rest comes from the manifest data.
92 int r
= pkg_create(d
, manifest
.c_str(), nullptr, false);
101 // This is a wrapper, for use only in stream-based output,
102 // that will output a string in UCL escaped fashion (in particular,
103 // quotes and backslashes are escaped). The list of characters
104 // to escape is taken from https://github.com/vstakhov/libucl
105 // (which is the reference implementation pkg(8) refers to).
109 const std::string
& value
;
111 EscapeQuotes(const std::string
& s
)
117 // Output a string as "string" with escaping applied.
118 cmGeneratedFileStream
& operator<<(cmGeneratedFileStream
& s
,
119 const EscapeQuotes
& v
)
122 for (char c
: v
.value
) {
154 // The following classes are all helpers for writing out the UCL
155 // manifest file (it also looks like JSON). ManifestKey just has
156 // a (string-valued) key; subclasses add a specific kind of
157 // value-type to the key, and implement write_value() to output
158 // the corresponding UCL.
164 ManifestKey(std::string k
)
169 virtual ~ManifestKey() = default;
171 // Output the value associated with this key to the stream @p s.
172 // Format is to be decided by subclasses.
173 virtual void write_value(cmGeneratedFileStream
& s
) const = 0;
176 // Basic string-value (e.g. "name": "cmake")
177 class ManifestKeyValue
: public ManifestKey
182 ManifestKeyValue(const std::string
& k
, std::string v
)
184 , value(std::move(v
))
188 void write_value(cmGeneratedFileStream
& s
) const override
190 s
<< EscapeQuotes(value
);
194 // List-of-strings values (e.g. "licenses": ["GPLv2", "LGPLv2"])
195 class ManifestKeyListValue
: public ManifestKey
198 using VList
= std::vector
<std::string
>;
201 ManifestKeyListValue(const std::string
& k
)
206 ManifestKeyListValue
& operator<<(const std::string
& v
)
212 ManifestKeyListValue
& operator<<(const std::vector
<std::string
>& v
)
214 for (std::string
const& e
: v
) {
220 void write_value(cmGeneratedFileStream
& s
) const override
222 bool with_comma
= false;
225 for (std::string
const& elem
: value
) {
226 s
<< (with_comma
? ',' : ' ');
227 s
<< EscapeQuotes(elem
);
234 // Deps: actually a dictionary, but we'll treat it as a
235 // list so we only name the deps, and produce dictionary-
236 // like output via write_value()
237 class ManifestKeyDepsValue
: public ManifestKeyListValue
240 ManifestKeyDepsValue(const std::string
& k
)
241 : ManifestKeyListValue(k
)
245 void write_value(cmGeneratedFileStream
& s
) const override
248 for (std::string
const& elem
: value
) {
249 s
<< " \"" << elem
<< R
"(": {"origin": ")" << elem
<< "\"},\n";
255 // Write one of the key-value classes (above) to the stream @p s
256 cmGeneratedFileStream
& operator<<(cmGeneratedFileStream
& s
,
257 const ManifestKey
& v
)
259 s
<< '"' << v
.key
<< "\": ";
265 // Look up variable; if no value is set, returns an empty string;
266 // basically a wrapper that handles the nullptr return from GetOption().
267 std::string
cmCPackFreeBSDGenerator::var_lookup(const char* var_name
)
269 cmValue pv
= this->GetOption(var_name
);
276 // Produce UCL in the given @p manifest file for the common
277 // manifest fields (common to the compact and regular formats),
278 // by reading the CPACK_FREEBSD_* variables.
279 void cmCPackFreeBSDGenerator::write_manifest_fields(
280 cmGeneratedFileStream
& manifest
)
282 manifest
<< ManifestKeyValue("name",
283 var_lookup("CPACK_FREEBSD_PACKAGE_NAME"));
284 manifest
<< ManifestKeyValue("origin",
285 var_lookup("CPACK_FREEBSD_PACKAGE_ORIGIN"));
286 manifest
<< ManifestKeyValue("version",
287 var_lookup("CPACK_FREEBSD_PACKAGE_VERSION"));
288 manifest
<< ManifestKeyValue("maintainer",
289 var_lookup("CPACK_FREEBSD_PACKAGE_MAINTAINER"));
290 manifest
<< ManifestKeyValue("comment",
291 var_lookup("CPACK_FREEBSD_PACKAGE_COMMENT"));
292 manifest
<< ManifestKeyValue(
293 "desc", var_lookup("CPACK_FREEBSD_PACKAGE_DESCRIPTION"));
294 manifest
<< ManifestKeyValue("www", var_lookup("CPACK_FREEBSD_PACKAGE_WWW"));
295 cmList licenses
{ var_lookup("CPACK_FREEBSD_PACKAGE_LICENSE") };
296 std::string
licenselogic("single");
297 if (licenses
.empty()) {
298 cmSystemTools::SetFatalErrorOccurred();
299 } else if (licenses
.size() > 1) {
300 licenselogic
= var_lookup("CPACK_FREEBSD_PACKAGE_LICENSE_LOGIC");
302 manifest
<< ManifestKeyValue("licenselogic", licenselogic
);
303 manifest
<< (ManifestKeyListValue("licenses") << licenses
);
304 cmList categories
{ var_lookup("CPACK_FREEBSD_PACKAGE_CATEGORIES") };
305 manifest
<< (ManifestKeyListValue("categories") << categories
);
306 manifest
<< ManifestKeyValue("prefix", var_lookup("CMAKE_INSTALL_PREFIX"));
307 cmList deps
{ var_lookup("CPACK_FREEBSD_PACKAGE_DEPS") };
309 manifest
<< (ManifestKeyDepsValue("deps") << deps
);
313 // Package only actual files; others are ignored (in particular,
314 // intermediate subdirectories are ignored).
315 static bool ignore_file(const std::string
& filename
)
318 return stat(filename
.c_str(), &statbuf
) < 0 ||
319 (statbuf
.st_mode
& S_IFMT
) != S_IFREG
;
322 // Write the given list of @p files to the manifest stream @p s,
323 // as the UCL field "files" (which is dictionary-valued, to
324 // associate filenames with hashes). All the files are transformed
325 // to paths relative to @p toplevel, with a leading / (since the paths
326 // in FreeBSD package files are supposed to be absolute).
327 void write_manifest_files(cmGeneratedFileStream
& s
,
328 const std::string
& toplevel
,
329 const std::vector
<std::string
>& files
)
331 s
<< "\"files\": {\n";
332 for (std::string
const& file
: files
) {
333 s
<< " \"/" << cmSystemTools::RelativePath(toplevel
, file
) << "\": \""
334 << "<sha256>" // this gets replaced by libpkg by the actual SHA256
340 int cmCPackFreeBSDGenerator::PackageFiles()
342 if (!this->ReadListFile("Internal/CPack/CPackFreeBSD.cmake")) {
343 cmCPackLogger(cmCPackLog::LOG_ERROR
,
344 "Error while executing CPackFreeBSD.cmake" << std::endl
);
348 cmWorkingDirectory
wd(toplevel
);
350 files
.erase(std::remove_if(files
.begin(), files
.end(), ignore_file
),
353 std::string manifestname
= toplevel
+ "/+MANIFEST";
355 cmGeneratedFileStream
manifest(manifestname
);
357 write_manifest_fields(manifest
);
358 write_manifest_files(manifest
, toplevel
, files
);
362 cmCPackLogger(cmCPackLog::LOG_DEBUG
, "Toplevel: " << toplevel
<< std::endl
);
364 if (WantsComponentInstallation()) {
365 // CASE 1 : COMPONENT ALL-IN-ONE package
366 // If ALL COMPONENTS in ONE package has been requested
367 // then the package file is unique and should be open here.
368 if (componentPackageMethod
== ONE_PACKAGE
) {
369 return PackageComponentsAllInOne();
371 // CASE 2 : COMPONENT CLASSICAL package(s) (i.e. not all-in-one)
372 // There will be 1 package for each component group
373 // however one may require to ignore component group and
374 // in this case you'll get 1 package for each component.
375 return PackageComponents(componentPackageMethod
==
376 ONE_PACKAGE_PER_COMPONENT
);
379 // There should be one name in the packageFileNames (already, see comment
380 // in cmCPackGenerator::DoPackage(), which holds what CPack guesses
381 // will be the package filename. libpkg does something else, though,
382 // so update the single filename to what we know will be right.
383 if (this->packageFileNames
.size() == 1) {
384 std::string currentPackage
= this->packageFileNames
[0];
385 auto lastSlash
= currentPackage
.rfind('/');
387 // If there is a pathname, preserve that; libpkg will write out
388 // a file with the package name and version as specified in the
389 // manifest, so we look those up (again). lastSlash is the slash
390 // itself, we need that as path separator to the calculated package name.
391 std::string actualPackage
=
392 ((lastSlash
!= std::string::npos
)
393 ? std::string(currentPackage
, 0, lastSlash
+ 1)
395 var_lookup("CPACK_FREEBSD_PACKAGE_NAME") + '-' +
396 var_lookup("CPACK_FREEBSD_PACKAGE_VERSION") + FreeBSDPackageSuffix_17
;
398 this->packageFileNames
.clear();
399 this->packageFileNames
.emplace_back(actualPackage
);
402 if (!pkg_initialized() && pkg_init(nullptr, nullptr) != EPKG_OK
) {
403 cmCPackLogger(cmCPackLog::LOG_ERROR
,
404 "Can not initialize FreeBSD libpkg." << std::endl
);
408 const std::string output_dir
= cmSystemTools::GetFilenamePath(toplevel
);
409 PkgCreate
package(output_dir
, toplevel
, manifestname
);
410 if (package
.isValid()) {
411 if (!package
.Create()) {
412 cmCPackLogger(cmCPackLog::LOG_ERROR
,
413 "Error during pkg_create()" << std::endl
);
417 cmCPackLogger(cmCPackLog::LOG_ERROR
,
418 "Error before pkg_create()" << std::endl
);
422 // Specifically looking for packages suffixed with the TAG
423 std::string broken_suffix_17
=
424 cmStrCat('-', var_lookup("CPACK_TOPLEVEL_TAG"), FreeBSDPackageSuffix_17
);
425 for (std::string
& name
: packageFileNames
) {
426 cmCPackLogger(cmCPackLog::LOG_DEBUG
, "Packagefile " << name
<< std::endl
);
427 if (cmHasSuffix(name
, broken_suffix_17
)) {
428 name
.replace(name
.size() - broken_suffix_17
.size(), std::string::npos
,
429 FreeBSDPackageSuffix_17
);
434 const std::string packageFileName
=
435 var_lookup("CPACK_PACKAGE_FILE_NAME") + FreeBSDPackageSuffix_17
;
436 if (packageFileNames
.size() == 1 && !packageFileName
.empty() &&
437 packageFileNames
[0] != packageFileName
) {
438 // Since libpkg always writes <name>-<version>.<suffix>,
439 // if there is a CPACK_PACKAGE_FILE_NAME set, we need to
440 // rename, and then re-set the name.
441 const std::string sourceFile
= packageFileNames
[0];
442 const std::string packageSubDirectory
=
443 cmSystemTools::GetParentDirectory(sourceFile
);
444 const std::string targetFileName
=
445 packageSubDirectory
+ '/' + packageFileName
;
446 if (cmSystemTools::RenameFile(sourceFile
, targetFileName
)) {
447 this->packageFileNames
.clear();
448 this->packageFileNames
.emplace_back(targetFileName
);