CPackDMG: Add support for multilingual SLAs
Multiple languages for SLAs and the SLA UI can be added via the CPack variables CPACK_DMG_SLA_DIR and CPACK_DMG_SLA_LANGUAGES. For each language defined in the languages variable, CPack will search for <language>.menu.txt and <language>.license.txt in CPACK_DMG_SLA_DIR. If the sla directory variable is not defined, the old behaviour using CPACK_RESOURCE_FILE_LICENSE is retained.
This commit is contained in:
parent
13dc7bdb5e
commit
f88533cc06
|
@ -0,0 +1,7 @@
|
|||
cpack-dmg-multilanguage-sla
|
||||
---------------------------
|
||||
|
||||
* The :module:`CPack DragNDrop generator <CPackDMG>` learned to add
|
||||
multi-lingual SLAs to a DMG which is presented to the user when they try to
|
||||
mount the DMG. See the :variable:`CPACK_DMG_SLA_LANGUAGES` and
|
||||
:variable:`CPACK_DMG_SLA_DIR` variables for details.
|
|
@ -36,6 +36,19 @@
|
|||
# background image is set. The background image is applied after applying the
|
||||
# custom .DS_Store file.
|
||||
#
|
||||
# .. variable:: CPACK_DMG_SLA_DIR
|
||||
#
|
||||
# Directory where license and menu files for different languages are stored.
|
||||
#
|
||||
# .. variable:: CPACK_DMG_SLA_LANGUAGES
|
||||
#
|
||||
# Languages for which a license agreement is provided when mounting the
|
||||
# generated DMG.
|
||||
#
|
||||
# For every language in this list, CPack will try to find files
|
||||
# ``<language>.menu.txt`` and ``<language>.license.txt`` in the directory
|
||||
# specified by the :variable:`CPACK_DMG_SLA_DIR` variable.
|
||||
#
|
||||
# .. variable:: CPACK_COMMAND_HDIUTIL
|
||||
#
|
||||
# Path to the hdiutil(1) command used to operate on disk image files on Mac
|
||||
|
|
|
@ -726,6 +726,9 @@ endif()
|
|||
# Build CPackLib
|
||||
add_library(CPackLib ${CPACK_SRCS})
|
||||
target_link_libraries(CPackLib CMakeLib)
|
||||
if(APPLE)
|
||||
target_link_libraries(CPackLib "-framework Carbon")
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
add_executable(cmakexbuild cmakexbuild.cxx)
|
||||
|
|
|
@ -18,6 +18,24 @@
|
|||
#include <cmsys/RegularExpression.hxx>
|
||||
#include <cmsys/FStream.hxx>
|
||||
|
||||
#include <iomanip>
|
||||
|
||||
#include <CoreFoundation/CFBase.h>
|
||||
#include <CoreFoundation/CFString.h>
|
||||
#include <CoreFoundation/CFLocale.h>
|
||||
|
||||
// The carbon framework is deprecated, but the Region codes it supplies are
|
||||
// needed for the LPic data structure used for generating multi-lingual SLAs.
|
||||
// There does not seem to be a replacement API for these region codes.
|
||||
#if defined(__clang__)
|
||||
# pragma clang diagnostic push
|
||||
# pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif
|
||||
#include <Carbon/Carbon.h>
|
||||
#if defined(__clang__)
|
||||
# pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
static const char* SLAHeader =
|
||||
"data 'LPic' (5000) {\n"
|
||||
" $\"0002 0011 0003 0001 0000 0000 0002 0000\"\n"
|
||||
|
@ -103,6 +121,69 @@ int cmCPackDragNDropGenerator::InitializeInternal()
|
|||
}
|
||||
this->SetOptionIfNotSet("CPACK_COMMAND_REZ", rez_path.c_str());
|
||||
|
||||
if(this->IsSet("CPACK_DMG_SLA_DIR"))
|
||||
{
|
||||
slaDirectory = this->GetOption("CPACK_DMG_SLA_DIR");
|
||||
if(!slaDirectory.empty() && this->IsSet("CPACK_RESOURCE_FILE_LICENSE"))
|
||||
{
|
||||
std::string license_file =
|
||||
this->GetOption("CPACK_RESOURCE_FILE_LICENSE");
|
||||
if(!license_file.empty() &&
|
||||
(license_file.find("CPack.GenericLicense.txt") == std::string::npos))
|
||||
{
|
||||
cmCPackLogger(cmCPackLog::LOG_WARNING,
|
||||
"Both CPACK_DMG_SLA_DIR and CPACK_RESOURCE_FILE_LICENSE specified, "
|
||||
"defaulting to CPACK_DMG_SLA_DIR"
|
||||
<< std::endl);
|
||||
}
|
||||
}
|
||||
if(!this->IsSet("CPACK_DMG_LANGUAGES"))
|
||||
{
|
||||
cmCPackLogger(cmCPackLog::LOG_ERROR,
|
||||
"CPACK_DMG_SLA_DIR set but no languages defined "
|
||||
"(set CPACK_DMG_LANGUAGES)"
|
||||
<< std::endl);
|
||||
return 0;
|
||||
}
|
||||
if(!cmSystemTools::FileExists(slaDirectory, false))
|
||||
{
|
||||
cmCPackLogger(cmCPackLog::LOG_ERROR,
|
||||
"CPACK_DMG_SLA_DIR does not exist"
|
||||
<< std::endl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<std::string> languages;
|
||||
cmSystemTools::ExpandListArgument(this->GetOption("CPACK_DMG_LANGUAGES"),
|
||||
languages);
|
||||
if(languages.empty())
|
||||
{
|
||||
cmCPackLogger(cmCPackLog::LOG_ERROR,
|
||||
"CPACK_DMG_LANGUAGES set but empty"
|
||||
<< std::endl);
|
||||
return 0;
|
||||
}
|
||||
for(size_t i = 0; i < languages.size(); ++i)
|
||||
{
|
||||
std::string license = slaDirectory + "/" + languages[i] + ".license.txt";
|
||||
if (!cmSystemTools::FileExists(license))
|
||||
{
|
||||
cmCPackLogger(cmCPackLog::LOG_ERROR,
|
||||
"Missing license file " << languages[i] << ".license.txt"
|
||||
<< std::endl);
|
||||
return 0;
|
||||
}
|
||||
std::string menu = slaDirectory + "/" + languages[i] + ".menu.txt";
|
||||
if (!cmSystemTools::FileExists(menu))
|
||||
{
|
||||
cmCPackLogger(cmCPackLog::LOG_ERROR,
|
||||
"Missing menu file " << languages[i] << ".menu.txt"
|
||||
<< std::endl);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this->Superclass::InitializeInternal();
|
||||
}
|
||||
|
||||
|
@ -246,6 +327,10 @@ int cmCPackDragNDropGenerator::CreateDMG(const std::string& src_dir,
|
|||
this->GetOption("CPACK_DMG_DS_STORE")
|
||||
? this->GetOption("CPACK_DMG_DS_STORE") : "";
|
||||
|
||||
const std::string cpack_dmg_languages =
|
||||
this->GetOption("CPACK_DMG_LANGUAGES")
|
||||
? this->GetOption("CPACK_DMG_LANGUAGES") : "";
|
||||
|
||||
// only put license on dmg if is user provided
|
||||
if(!cpack_license_file.empty() &&
|
||||
cpack_license_file.find("CPack.GenericLicense.txt") != std::string::npos)
|
||||
|
@ -253,6 +338,13 @@ int cmCPackDragNDropGenerator::CreateDMG(const std::string& src_dir,
|
|||
cpack_license_file = "";
|
||||
}
|
||||
|
||||
// use sla_dir if both sla_dir and license_file are set
|
||||
if(!cpack_license_file.empty() &&
|
||||
!slaDirectory.empty())
|
||||
{
|
||||
cpack_license_file = "";
|
||||
}
|
||||
|
||||
// The staging directory contains everything that will end-up inside the
|
||||
// final disk image ...
|
||||
std::ostringstream staging;
|
||||
|
@ -418,54 +510,122 @@ int cmCPackDragNDropGenerator::CreateDMG(const std::string& src_dir,
|
|||
}
|
||||
}
|
||||
|
||||
if(!cpack_license_file.empty())
|
||||
if(!cpack_license_file.empty() || !slaDirectory.empty())
|
||||
{
|
||||
// Use old hardcoded style if sla_dir is not set
|
||||
bool oldStyle = slaDirectory.empty();
|
||||
std::string sla_r = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
|
||||
sla_r += "/sla.r";
|
||||
|
||||
cmsys::ifstream ifs;
|
||||
ifs.open(cpack_license_file.c_str());
|
||||
if(ifs.is_open())
|
||||
std::vector<std::string> languages;
|
||||
if(!oldStyle)
|
||||
{
|
||||
cmGeneratedFileStream osf(sla_r.c_str());
|
||||
osf << "#include <CoreServices/CoreServices.r>\n\n";
|
||||
osf << SLAHeader;
|
||||
osf << "\n";
|
||||
osf << "data 'TEXT' (5002, \"English\") {\n";
|
||||
while(ifs.good())
|
||||
{
|
||||
std::string line;
|
||||
std::getline(ifs, line);
|
||||
// escape quotes
|
||||
std::string::size_type pos = line.find('\"');
|
||||
while(pos != std::string::npos)
|
||||
{
|
||||
line.replace(pos, 1, "\\\"");
|
||||
pos = line.find('\"', pos+2);
|
||||
}
|
||||
// break up long lines to avoid Rez errors
|
||||
std::vector<std::string> lines;
|
||||
const size_t max_line_length = 512;
|
||||
for(size_t i=0; i<line.size(); i+= max_line_length)
|
||||
{
|
||||
int line_length = max_line_length;
|
||||
if(i+max_line_length > line.size())
|
||||
line_length = line.size()-i;
|
||||
lines.push_back(line.substr(i, line_length));
|
||||
cmSystemTools::ExpandListArgument(cpack_dmg_languages, languages);
|
||||
}
|
||||
|
||||
for(size_t i=0; i<lines.size(); i++)
|
||||
cmGeneratedFileStream ofs(sla_r.c_str());
|
||||
ofs << "#include <CoreServices/CoreServices.r>\n\n";
|
||||
if(oldStyle)
|
||||
{
|
||||
osf << " \"" << lines[i] << "\"\n";
|
||||
ofs << SLAHeader;
|
||||
ofs << "\n";
|
||||
}
|
||||
osf << " \"\\n\"\n";
|
||||
else
|
||||
{
|
||||
/*
|
||||
* LPic Layout
|
||||
* (https://github.com/pypt/dmg-add-license/blob/master/main.c)
|
||||
* as far as I can tell (no official documentation seems to exist):
|
||||
* struct LPic {
|
||||
* uint16_t default_language; // points to a resid, defaulting to 0,
|
||||
* // which is the first set language
|
||||
* uint16_t length;
|
||||
* struct {
|
||||
* uint16_t language_code;
|
||||
* uint16_t resid;
|
||||
* uint16_t encoding; // Encoding from TextCommon.h,
|
||||
* // forcing MacRoman (0) for now. Might need to
|
||||
* // allow overwrite per license by user later
|
||||
* } item[1];
|
||||
* }
|
||||
*/
|
||||
|
||||
// Create vector first for readability, then iterate to write to ofs
|
||||
std::vector<uint16_t> header_data;
|
||||
header_data.push_back(0);
|
||||
header_data.push_back(languages.size());
|
||||
for(size_t i = 0; i < languages.size(); ++i)
|
||||
{
|
||||
CFStringRef language_cfstring = CFStringCreateWithCString(
|
||||
NULL, languages[i].c_str(), kCFStringEncodingUTF8);
|
||||
CFStringRef iso_language =
|
||||
CFLocaleCreateCanonicalLanguageIdentifierFromString(
|
||||
NULL, language_cfstring);
|
||||
if (!iso_language)
|
||||
{
|
||||
cmCPackLogger(cmCPackLog::LOG_ERROR,
|
||||
languages[i] << " is not a recognized language"
|
||||
<< std::endl);
|
||||
}
|
||||
osf << "};\n";
|
||||
osf << "\n";
|
||||
osf << SLASTREnglish;
|
||||
ifs.close();
|
||||
osf.close();
|
||||
char *iso_language_cstr = (char *) malloc(65);
|
||||
CFStringGetCString(iso_language, iso_language_cstr, 64,
|
||||
kCFStringEncodingMacRoman);
|
||||
LangCode lang = 0;
|
||||
RegionCode region = 0;
|
||||
OSStatus err = LocaleStringToLangAndRegionCodes(iso_language_cstr,
|
||||
&lang, ®ion);
|
||||
if (err != noErr)
|
||||
{
|
||||
cmCPackLogger(cmCPackLog::LOG_ERROR,
|
||||
"No language/region code available for " << iso_language_cstr
|
||||
<< std::endl);
|
||||
free(iso_language_cstr);
|
||||
return 0;
|
||||
}
|
||||
free(iso_language_cstr);
|
||||
header_data.push_back(region);
|
||||
header_data.push_back(i);
|
||||
header_data.push_back(0);
|
||||
}
|
||||
ofs << "data 'LPic' (5000) {\n";
|
||||
ofs << std::hex << std::uppercase << std::setfill('0');
|
||||
|
||||
for(size_t i = 0; i < header_data.size(); ++i)
|
||||
{
|
||||
if(i % 8 == 0)
|
||||
{
|
||||
ofs << " $\"";
|
||||
}
|
||||
|
||||
ofs << std::setw(4) << header_data[i];
|
||||
|
||||
if(i % 8 == 7 || i == header_data.size() - 1)
|
||||
{
|
||||
ofs << "\"\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ofs << " ";
|
||||
}
|
||||
}
|
||||
ofs << "};\n\n";
|
||||
// Reset ofs options
|
||||
ofs << std::dec << std::nouppercase << std::setfill(' ');
|
||||
}
|
||||
|
||||
if(oldStyle)
|
||||
{
|
||||
WriteLicense(ofs, 0, "", cpack_license_file);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(size_t i = 0; i < languages.size(); ++i)
|
||||
{
|
||||
WriteLicense(ofs, i + 5000, languages[i]);
|
||||
}
|
||||
}
|
||||
|
||||
ofs.Close();
|
||||
|
||||
// convert to UDCO
|
||||
std::string temp_udco = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
|
||||
|
@ -607,3 +767,126 @@ cmCPackDragNDropGenerator::GetComponentInstallDirNameSuffix(
|
|||
|
||||
return GetComponentPackageFileName(package_file_name, componentName, false);
|
||||
}
|
||||
|
||||
void
|
||||
cmCPackDragNDropGenerator::WriteLicense(cmGeneratedFileStream& outputStream,
|
||||
int licenseNumber, std::string licenseLanguage, std::string licenseFile)
|
||||
{
|
||||
if(!licenseFile.empty())
|
||||
{
|
||||
licenseNumber = 5002;
|
||||
licenseLanguage = "English";
|
||||
}
|
||||
|
||||
// License header
|
||||
outputStream << "data 'TEXT' (" << licenseNumber << ", \""
|
||||
<< licenseLanguage << "\") {\n";
|
||||
// License body
|
||||
std::string actual_license = !licenseFile.empty() ? licenseFile :
|
||||
(slaDirectory + "/" + licenseLanguage + ".license.txt");
|
||||
cmsys::ifstream license_ifs;
|
||||
license_ifs.open(actual_license.c_str());
|
||||
if(license_ifs.is_open())
|
||||
{
|
||||
while(license_ifs.good())
|
||||
{
|
||||
std::string line;
|
||||
std::getline(license_ifs, line);
|
||||
if(!line.empty())
|
||||
{
|
||||
EscapeQuotes(line);
|
||||
std::vector<std::string> lines;
|
||||
BreakLongLine(line, lines);
|
||||
for(size_t i = 0; i < lines.size(); ++i)
|
||||
{
|
||||
outputStream << " \"" << lines[i] << "\"\n";
|
||||
}
|
||||
}
|
||||
outputStream << " \"\\n\"\n";
|
||||
}
|
||||
license_ifs.close();
|
||||
}
|
||||
|
||||
// End of License
|
||||
outputStream << "};\n\n";
|
||||
if(!licenseFile.empty())
|
||||
{
|
||||
outputStream << SLASTREnglish;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Menu header
|
||||
outputStream << "resource 'STR#' (" << licenseNumber << ", \""
|
||||
<< licenseLanguage << "\") {\n";
|
||||
outputStream << " {\n";
|
||||
|
||||
// Menu body
|
||||
cmsys::ifstream menu_ifs;
|
||||
menu_ifs.open((slaDirectory+"/"+licenseLanguage+".menu.txt").c_str());
|
||||
if(menu_ifs.is_open())
|
||||
{
|
||||
size_t lines_written = 0;
|
||||
while(menu_ifs.good())
|
||||
{
|
||||
// Lines written from original file, not from broken up lines
|
||||
std::string line;
|
||||
std::getline(menu_ifs, line);
|
||||
if(!line.empty())
|
||||
{
|
||||
EscapeQuotes(line);
|
||||
std::vector<std::string> lines;
|
||||
BreakLongLine(line, lines);
|
||||
for(size_t i = 0; i < lines.size(); ++i)
|
||||
{
|
||||
std::string comma;
|
||||
// We need a comma after every complete string,
|
||||
// but not on the very last line
|
||||
if(lines_written != 8 && i == lines.size() - 1)
|
||||
{
|
||||
comma = ",";
|
||||
}
|
||||
else
|
||||
{
|
||||
comma = "";
|
||||
}
|
||||
outputStream << " \"" << lines[i] << "\"" << comma << "\n";
|
||||
}
|
||||
++lines_written;
|
||||
}
|
||||
}
|
||||
menu_ifs.close();
|
||||
}
|
||||
|
||||
//End of menu
|
||||
outputStream << " }\n";
|
||||
outputStream << "};\n";
|
||||
outputStream << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
cmCPackDragNDropGenerator::BreakLongLine(const std::string& line,
|
||||
std::vector<std::string>& lines)
|
||||
{
|
||||
const size_t max_line_length = 512;
|
||||
for(size_t i = 0; i < line.size(); i += max_line_length)
|
||||
{
|
||||
int line_length = max_line_length;
|
||||
if(i + max_line_length > line.size())
|
||||
{
|
||||
line_length = line.size() - i;
|
||||
}
|
||||
lines.push_back(line.substr(i, line_length));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
cmCPackDragNDropGenerator::EscapeQuotes(std::string& line)
|
||||
{
|
||||
std::string::size_type pos = line.find('\"');
|
||||
while(pos != std::string::npos)
|
||||
{
|
||||
line.replace(pos, 1, "\\\"");
|
||||
pos = line.find('\"', pos + 2);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
|
||||
#include "cmCPackGenerator.h"
|
||||
|
||||
class cmGeneratedFileStream;
|
||||
|
||||
/** \class cmCPackDragNDropGenerator
|
||||
* \brief A generator for OSX drag-n-drop installs
|
||||
*/
|
||||
|
@ -42,6 +44,15 @@ protected:
|
|||
int CreateDMG(const std::string& src_dir, const std::string& output_file);
|
||||
|
||||
std::string InstallPrefix;
|
||||
|
||||
private:
|
||||
std::string slaDirectory;
|
||||
|
||||
void WriteLicense(cmGeneratedFileStream& outputStream, int licenseNumber,
|
||||
std::string licenseLanguage, std::string licenseFile = "");
|
||||
void BreakLongLine(const std::string& line,
|
||||
std::vector<std::string>& lines);
|
||||
void EscapeQuotes(std::string& line);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue