From bed79f6c6b47b2c0661da67799ec37fce8905c54 Mon Sep 17 00:00:00 2001 From: Brad King Date: Mon, 21 Aug 2006 16:55:03 -0400 Subject: ENH: Implemented INSTALL(DIRECTORY) command and added a test. Re-organized cmFileCommand's implementation of FILE(INSTALL) a bit to help out. This addresses bug#1694 and partially addresses bug#2691. --- Source/cmFileCommand.cxx | 662 +++++++++++++++--------- Source/cmInstallCommand.cxx | 178 ++++++- Source/cmInstallCommand.h | 49 ++ Source/cmInstallDirectoryGenerator.cxx | 8 +- Source/cmInstallDirectoryGenerator.h | 4 +- Source/cmInstallGenerator.cxx | 10 +- Source/cmInstallGenerator.h | 3 +- Source/cmInstallTargetGenerator.cxx | 6 +- Tests/SimpleInstall/CMakeLists.txt | 23 + Tests/SimpleInstall/scripts/.gitattributes | 1 + Tests/SimpleInstall/scripts/sample_script | 2 + Tests/SimpleInstall/scripts/sample_script.bat | 1 + Tests/SimpleInstallS2/CMakeLists.txt | 23 + Tests/SimpleInstallS2/scripts/.gitattributes | 1 + Tests/SimpleInstallS2/scripts/sample_script | 2 + Tests/SimpleInstallS2/scripts/sample_script.bat | 1 + 16 files changed, 727 insertions(+), 247 deletions(-) create mode 100644 Tests/SimpleInstall/scripts/.gitattributes create mode 100755 Tests/SimpleInstall/scripts/sample_script create mode 100755 Tests/SimpleInstall/scripts/sample_script.bat create mode 100644 Tests/SimpleInstallS2/scripts/.gitattributes create mode 100755 Tests/SimpleInstallS2/scripts/sample_script create mode 100755 Tests/SimpleInstallS2/scripts/sample_script.bat diff --git a/Source/cmFileCommand.cxx b/Source/cmFileCommand.cxx index c855418..ee83ea5 100644 --- a/Source/cmFileCommand.cxx +++ b/Source/cmFileCommand.cxx @@ -20,6 +20,34 @@ #include #include #include +#include + +// Table of permissions flags. +#if defined(_WIN32) && !defined(__CYGWIN__) +static mode_t mode_owner_read = S_IREAD; +static mode_t mode_owner_write = S_IWRITE; +static mode_t mode_owner_execute = S_IEXEC; +static mode_t mode_group_read = 0; +static mode_t mode_group_write = 0; +static mode_t mode_group_execute = 0; +static mode_t mode_world_read = 0; +static mode_t mode_world_write = 0; +static mode_t mode_world_execute = 0; +static mode_t mode_setuid = 0; +static mode_t mode_setgid = 0; +#else +static mode_t mode_owner_read = S_IRUSR; +static mode_t mode_owner_write = S_IWUSR; +static mode_t mode_owner_execute = S_IXUSR; +static mode_t mode_group_read = S_IRGRP; +static mode_t mode_group_write = S_IWGRP; +static mode_t mode_group_execute = S_IXGRP; +static mode_t mode_world_read = S_IROTH; +static mode_t mode_world_write = S_IWOTH; +static mode_t mode_world_execute = S_IXOTH; +static mode_t mode_setuid = S_ISUID; +static mode_t mode_setgid = S_ISGID; +#endif // cmLibraryCommand bool cmFileCommand::InitialPass(std::vector const& args) @@ -316,27 +344,120 @@ bool cmFileCommand::HandleMakeDirectoryCommand( } //---------------------------------------------------------------------------- +// File installation helper class. struct cmFileInstaller { - bool InstallFile(const char* fromFile, const char* toFile, bool always, - bool no_permissions); - bool InstallDirectory(const char* source, - const char* destination, - bool always, - std::string& smanifest_files, - int destDirLength); + // Methods to actually install files. + bool InstallFile(const char* fromFile, const char* toFile, bool always); + bool InstallDirectory(const char* source, const char* destination, + bool always); + + // All instances need the file command and makefile using them. cmFileInstaller(cmFileCommand* fc, cmMakefile* mf): - FileCommand(fc), Makefile(mf) {} + FileCommand(fc), Makefile(mf), DestDirLength(0) + { + // Get the current manifest. + this->Manifest = + this->Makefile->GetSafeDefinition("CMAKE_INSTALL_MANIFEST_FILES"); + } + ~cmFileInstaller() + { + // Save the updated install manifest. + this->Makefile->AddDefinition("CMAKE_INSTALL_MANIFEST_FILES", + this->Manifest.c_str()); + } + +private: cmFileCommand* FileCommand; cmMakefile* Makefile; +public: + + // The length of the destdir setting. + int DestDirLength; + + // The current file manifest (semicolon separated list). + std::string Manifest; + + // Permissions for files and directories installed by this object. mode_t FilePermissions; mode_t DirPermissions; + + // Properties set by pattern and regex match rules. + struct MatchProperties + { + bool Exclude; + mode_t Permissions; + MatchProperties(): Exclude(false), Permissions(0) {} + }; + struct MatchRule + { + cmsys::RegularExpression Regex; + MatchProperties Properties; + MatchRule(std::string const& regex): Regex(regex.c_str()) {} + }; + std::vector MatchRules; + + // Get the properties from rules matching this input file. + MatchProperties CollectMatchProperties(const char* file) + { + MatchProperties result; + for(std::vector::iterator mr = this->MatchRules.begin(); + mr != this->MatchRules.end(); ++mr) + { + if(mr->Regex.find(file)) + { + result.Exclude |= mr->Properties.Exclude; + result.Permissions |= mr->Properties.Permissions; + } + } + return result; + } + + // Append a file to the installation manifest. + void ManifestAppend(std::string const& file) + { + this->Manifest += ";"; + this->Manifest += file.substr(this->DestDirLength); + } + + // Translate an argument to a permissions bit. + bool CheckPermissions(std::string const& arg, mode_t& permissions) + { + if(arg == "OWNER_READ") { permissions |= mode_owner_read; } + else if(arg == "OWNER_WRITE") { permissions |= mode_owner_write; } + else if(arg == "OWNER_EXECUTE") { permissions |= mode_owner_execute; } + else if(arg == "GROUP_READ") { permissions |= mode_group_read; } + else if(arg == "GROUP_WRITE") { permissions |= mode_group_write; } + else if(arg == "GROUP_EXECUTE") { permissions |= mode_group_execute; } + else if(arg == "WORLD_READ") { permissions |= mode_world_read; } + else if(arg == "WORLD_WRITE") { permissions |= mode_world_write; } + else if(arg == "WORLD_EXECUTE") { permissions |= mode_world_execute; } + else if(arg == "SETUID") { permissions |= mode_setuid; } + else if(arg == "SETGID") { permissions |= mode_setgid; } + else + { + cmOStringStream e; + e << "INSTALL given invalid permission \"" << arg << "\"."; + this->FileCommand->SetError(e.str().c_str()); + return false; + } + return true; + } }; //---------------------------------------------------------------------------- bool cmFileInstaller::InstallFile(const char* fromFile, const char* toFile, - bool always, bool no_permissions) + bool always) { + // Collect any properties matching this file name. + MatchProperties match_properties = this->CollectMatchProperties(fromFile); + + // Skip the file if it is excluded. + if(match_properties.Exclude) + { + return true; + } + // Inform the user about this file installation. std::string message = "Installing "; message += toFile; @@ -352,11 +473,19 @@ bool cmFileInstaller::InstallFile(const char* fromFile, const char* toFile, return false; } + // Add the file to the manifest. + this->ManifestAppend(toFile); + // Set permissions of the destination file. - // TODO: Take out no_permissions and replace with a user option to - // preserve source permissions explicitly. - if(!no_permissions && - !cmSystemTools::SetPermissions(toFile, this->FilePermissions)) + mode_t permissions = (match_properties.Permissions? + match_properties.Permissions : this->FilePermissions); + if(!permissions) + { + // No permissions were explicitly provided but the user requested + // that the source file permissions be used. + cmSystemTools::GetPermissions(fromFile, permissions); + } + if(permissions && !cmSystemTools::SetPermissions(toFile, permissions)) { cmOStringStream e; e << "Problem setting permissions on file \"" << toFile << "\""; @@ -370,19 +499,67 @@ bool cmFileInstaller::InstallFile(const char* fromFile, const char* toFile, //---------------------------------------------------------------------------- bool cmFileInstaller::InstallDirectory(const char* source, const char* destination, - bool always, - std::string& smanifest_files, - int destDirLength) + bool always) { - cmsys::Directory dir; - dir.Load(source); + // Collect any properties matching this directory name. + MatchProperties match_properties = this->CollectMatchProperties(source); + + // Skip the directory if it is excluded. + if(match_properties.Exclude) + { + return true; + } + + // Make sure the destination directory exists. if(!cmSystemTools::MakeDirectory(destination)) { return false; } - // TODO: Make sure destination directory has write permissions - // before installing files. User requested permissions may be - // restored later. + + // Compute the requested permissions for the destination directory. + mode_t permissions = (match_properties.Permissions? + match_properties.Permissions : this->DirPermissions); + if(!permissions) + { + // No permissions were explicitly provided but the user requested + // that the source directory permissions be used. + cmSystemTools::GetPermissions(source, permissions); + } + + // Compute the set of permissions required on this directory to + // recursively install files and subdirectories safely. + mode_t required_permissions = + mode_owner_read | mode_owner_write | mode_owner_execute; + + // If the required permissions are specified it is safe to set the + // final permissions now. Otherwise we must add the required + // permissions temporarily during file installation. + mode_t permissions_before = 0; + mode_t permissions_after = 0; + if(permissions & required_permissions) + { + permissions_before = permissions; + } + else + { + permissions_before = permissions | required_permissions; + permissions_after = permissions; + } + + // Set the required permissions of the destination directory. + if(permissions_before && + !cmSystemTools::SetPermissions(destination, permissions_before)) + { + cmOStringStream e; + e << "Problem setting permissions on directory \"" + << destination << "\""; + this->FileCommand->SetError(e.str().c_str()); + return false; + } + + // Load the directory contents to traverse it recursively. + cmsys::Directory dir; + dir.Load(source); unsigned long numFiles = static_cast(dir.GetNumberOfFiles()); for(unsigned long fileNum = 0; fileNum < numFiles; ++fileNum) { @@ -397,8 +574,7 @@ bool cmFileInstaller::InstallDirectory(const char* source, kwsys_stl::string toDir = destination; toDir += "/"; toDir += dir.GetFile(fileNum); - if(!this->InstallDirectory(fromPath.c_str(), toDir.c_str(), always, - smanifest_files, destDirLength)) + if(!this->InstallDirectory(fromPath.c_str(), toDir.c_str(), always)) { return false; } @@ -409,12 +585,7 @@ bool cmFileInstaller::InstallDirectory(const char* source, std::string toFile = destination; toFile += "/"; toFile += dir.GetFile(fileNum); - if(this->InstallFile(fromPath.c_str(), toFile.c_str(), always, true)) - { - smanifest_files += ";"; - smanifest_files += toFile.substr(destDirLength); - } - else + if(!this->InstallFile(fromPath.c_str(), toFile.c_str(), always)) { return false; } @@ -422,8 +593,9 @@ bool cmFileInstaller::InstallDirectory(const char* source, } } - // Set the requested permissions on the destination directory. - if(!cmSystemTools::SetPermissions(destination, this->DirPermissions)) + // Set the requested permissions of the destination directory. + if(permissions_after && + !cmSystemTools::SetPermissions(destination, permissions_after)) { cmOStringStream e; e << "Problem setting permissions on directory \"" << destination << "\""; @@ -444,6 +616,9 @@ bool cmFileCommand::HandleInstallCommand( return false; } + // Construct a file installer object. + cmFileInstaller installer(this, this->Makefile); + std::string rename = ""; std::string destination = ""; std::string stype = "FILES"; @@ -459,60 +634,52 @@ bool cmFileCommand::HandleInstallCommand( std::map properties; - // Build a table of permissions flags. -#if defined(_WIN32) && !defined(__CYGWIN__) - mode_t mode_owner_read = S_IREAD; - mode_t mode_owner_write = S_IWRITE; - mode_t mode_owner_execute = S_IEXEC; - mode_t mode_group_read = 0; - mode_t mode_group_write = 0; - mode_t mode_group_execute = 0; - mode_t mode_world_read = 0; - mode_t mode_world_write = 0; - mode_t mode_world_execute = 0; - mode_t mode_setuid = 0; - mode_t mode_setgid = 0; -#else - mode_t mode_owner_read = S_IRUSR; - mode_t mode_owner_write = S_IWUSR; - mode_t mode_owner_execute = S_IXUSR; - mode_t mode_group_read = S_IRGRP; - mode_t mode_group_write = S_IWGRP; - mode_t mode_group_execute = S_IXGRP; - mode_t mode_world_read = S_IROTH; - mode_t mode_world_write = S_IWOTH; - mode_t mode_world_execute = S_IXOTH; - mode_t mode_setuid = S_ISUID; - mode_t mode_setgid = S_ISGID; -#endif - - bool in_files = false; - bool in_properties = false; - bool in_permissions_file = false; - bool in_permissions_dir = false; - bool in_components = false; - bool in_configurations = false; + bool doing_files = false; + bool doing_properties = false; + bool doing_permissions_file = false; + bool doing_permissions_dir = false; + bool doing_permissions_match = false; + bool doing_components = false; + bool doing_configurations = false; bool use_given_permissions_file = false; bool use_given_permissions_dir = false; + bool use_source_permissions = false; mode_t permissions_file = 0; mode_t permissions_dir = 0; bool optional = false; + cmFileInstaller::MatchRule* current_match_rule = 0; for ( ; i != args.size(); ++i ) { const std::string* cstr = &args[i]; if ( *cstr == "DESTINATION" && i < args.size()-1 ) { + if(current_match_rule) + { + cmOStringStream e; + e << "INSTALL does not allow \"" << *cstr << "\" after REGEX."; + this->SetError(e.str().c_str()); + return false; + } + i++; destination = args[i]; - in_files = false; - in_properties = false; - in_permissions_file = false; - in_permissions_dir = false; - in_components = false; - in_configurations = false; + doing_files = false; + doing_properties = false; + doing_permissions_file = false; + doing_permissions_dir = false; + doing_components = false; + doing_configurations = false; } else if ( *cstr == "TYPE" && i < args.size()-1 ) { + if(current_match_rule) + { + cmOStringStream e; + e << "INSTALL does not allow \"" << *cstr << "\" after REGEX."; + this->SetError(e.str().c_str()); + return false; + } + i++; stype = args[i]; if ( args[i+1] == "OPTIONAL" ) @@ -520,184 +687,226 @@ bool cmFileCommand::HandleInstallCommand( i++; optional = true; } - in_properties = false; - in_files = false; - in_permissions_file = false; - in_permissions_dir = false; - in_components = false; - in_configurations = false; + doing_properties = false; + doing_files = false; + doing_permissions_file = false; + doing_permissions_dir = false; + doing_components = false; + doing_configurations = false; } else if ( *cstr == "RENAME" && i < args.size()-1 ) { + if(current_match_rule) + { + cmOStringStream e; + e << "INSTALL does not allow \"" << *cstr << "\" after REGEX."; + this->SetError(e.str().c_str()); + return false; + } + i++; rename = args[i]; - in_properties = false; - in_files = false; - in_permissions_file = false; - in_permissions_dir = false; - in_components = false; - in_configurations = false; + doing_properties = false; + doing_files = false; + doing_permissions_file = false; + doing_permissions_dir = false; + doing_components = false; + doing_configurations = false; + } + else if ( *cstr == "REGEX" && i < args.size()-1 ) + { + i++; + installer.MatchRules.push_back(cmFileInstaller::MatchRule(args[i])); + current_match_rule = &*(installer.MatchRules.end()-1); + if(!current_match_rule->Regex.is_valid()) + { + cmOStringStream e; + e << "INSTALL could not compile REGEX \"" << args[i] << "\"."; + this->SetError(e.str().c_str()); + return false; + } + doing_properties = false; + doing_files = false; + doing_permissions_file = false; + doing_permissions_dir = false; + doing_components = false; + doing_configurations = false; + } + else if ( *cstr == "EXCLUDE" ) + { + // Add this property to the current match rule. + if(!current_match_rule) + { + cmOStringStream e; + e << "INSTALL does not allow \"" + << *cstr << "\" before a REGEX is given."; + this->SetError(e.str().c_str()); + return false; + } + current_match_rule->Properties.Exclude = true; + doing_permissions_match = true; } else if ( *cstr == "PROPERTIES" ) { - in_properties = true; - in_files = false; - in_permissions_file = false; - in_permissions_dir = false; - in_components = false; - in_configurations = false; + if(current_match_rule) + { + cmOStringStream e; + e << "INSTALL does not allow \"" << *cstr << "\" after REGEX."; + this->SetError(e.str().c_str()); + return false; + } + + doing_properties = true; + doing_files = false; + doing_permissions_file = false; + doing_permissions_dir = false; + doing_components = false; + doing_configurations = false; } else if ( *cstr == "PERMISSIONS" ) { - use_given_permissions_file = true; - in_properties = false; - in_files = false; - in_permissions_file = true; - in_permissions_dir = false; - in_components = false; - in_configurations = false; + if(current_match_rule) + { + doing_permissions_match = true; + doing_permissions_file = false; + } + else + { + doing_permissions_match = false; + doing_permissions_file = true; + use_given_permissions_file = true; + } + doing_properties = false; + doing_files = false; + doing_permissions_dir = false; + doing_components = false; + doing_configurations = false; } else if ( *cstr == "DIR_PERMISSIONS" ) { + if(current_match_rule) + { + cmOStringStream e; + e << "INSTALL does not allow \"" << *cstr << "\" after REGEX."; + this->SetError(e.str().c_str()); + return false; + } + use_given_permissions_dir = true; - in_properties = false; - in_files = false; - in_permissions_file = false; - in_permissions_dir = true; - in_components = false; - in_configurations = false; + doing_properties = false; + doing_files = false; + doing_permissions_file = false; + doing_permissions_dir = true; + doing_components = false; + doing_configurations = false; + } + else if ( *cstr == "USE_SOURCE_PERMISSIONS" ) + { + if(current_match_rule) + { + cmOStringStream e; + e << "INSTALL does not allow \"" << *cstr << "\" after REGEX."; + this->SetError(e.str().c_str()); + return false; + } + + doing_properties = false; + doing_files = false; + doing_permissions_file = false; + doing_permissions_dir = false; + doing_components = false; + doing_configurations = false; + use_source_permissions = true; } else if ( *cstr == "COMPONENTS" ) { - in_properties = false; - in_files = false; - in_permissions_file = false; - in_permissions_dir = false; - in_components = true; - in_configurations = false; + if(current_match_rule) + { + cmOStringStream e; + e << "INSTALL does not allow \"" << *cstr << "\" after REGEX."; + this->SetError(e.str().c_str()); + return false; + } + + doing_properties = false; + doing_files = false; + doing_permissions_file = false; + doing_permissions_dir = false; + doing_components = true; + doing_configurations = false; } else if ( *cstr == "CONFIGURATIONS" ) { - in_properties = false; - in_files = false; - in_permissions_file = false; - in_permissions_dir = false; - in_components = false; - in_configurations = true; + if(current_match_rule) + { + cmOStringStream e; + e << "INSTALL does not allow \"" << *cstr << "\" after REGEX."; + this->SetError(e.str().c_str()); + return false; + } + + doing_properties = false; + doing_files = false; + doing_permissions_file = false; + doing_permissions_dir = false; + doing_components = false; + doing_configurations = true; } - else if ( *cstr == "FILES" && !in_files) + else if ( *cstr == "FILES" && !doing_files) { - in_files = true; - in_properties = false; - in_permissions_file = false; - in_permissions_dir = false; - in_components = false; - in_configurations = false; + if(current_match_rule) + { + cmOStringStream e; + e << "INSTALL does not allow \"" << *cstr << "\" after REGEX."; + this->SetError(e.str().c_str()); + return false; + } + + doing_files = true; + doing_properties = false; + doing_permissions_file = false; + doing_permissions_dir = false; + doing_components = false; + doing_configurations = false; } - else if ( in_properties && i < args.size()-1 ) + else if ( doing_properties && i < args.size()-1 ) { properties[args[i]] = args[i+1].c_str(); i++; } - else if ( in_files ) + else if ( doing_files ) { files.push_back(*cstr); } - else if ( in_components ) + else if ( doing_components ) { components.insert(*cstr); } - else if ( in_configurations ) + else if ( doing_configurations ) { configurations.insert(cmSystemTools::UpperCase(*cstr)); } - else if(in_permissions_file && args[i] == "OWNER_READ") - { - permissions_file |= mode_owner_read; - } - else if(in_permissions_file && args[i] == "OWNER_WRITE") + else if(doing_permissions_file) { - permissions_file |= mode_owner_write; - } - else if(in_permissions_file && args[i] == "OWNER_EXECUTE") - { - permissions_file |= mode_owner_execute; - } - else if(in_permissions_file && args[i] == "GROUP_READ") - { - permissions_file |= mode_group_read; - } - else if(in_permissions_file && args[i] == "GROUP_WRITE") - { - permissions_file |= mode_group_write; - } - else if(in_permissions_file && args[i] == "GROUP_EXECUTE") - { - permissions_file |= mode_group_execute; - } - else if(in_permissions_file && args[i] == "WORLD_READ") - { - permissions_file |= mode_world_read; - } - else if(in_permissions_file && args[i] == "WORLD_WRITE") - { - permissions_file |= mode_world_write; - } - else if(in_permissions_file && args[i] == "WORLD_EXECUTE") - { - permissions_file |= mode_world_execute; - } - else if(in_permissions_file && args[i] == "SETUID") - { - permissions_file |= mode_setuid; - } - else if(in_permissions_file && args[i] == "SETGID") - { - permissions_file |= mode_setgid; - } - else if(in_permissions_dir && args[i] == "OWNER_READ") - { - permissions_dir |= mode_owner_read; - } - else if(in_permissions_dir && args[i] == "OWNER_WRITE") - { - permissions_dir |= mode_owner_write; - } - else if(in_permissions_dir && args[i] == "OWNER_EXECUTE") - { - permissions_dir |= mode_owner_execute; - } - else if(in_permissions_dir && args[i] == "GROUP_READ") - { - permissions_dir |= mode_group_read; - } - else if(in_permissions_dir && args[i] == "GROUP_WRITE") - { - permissions_dir |= mode_group_write; - } - else if(in_permissions_dir && args[i] == "GROUP_EXECUTE") - { - permissions_dir |= mode_group_execute; - } - else if(in_permissions_dir && args[i] == "WORLD_READ") - { - permissions_dir |= mode_world_read; - } - else if(in_permissions_dir && args[i] == "WORLD_WRITE") - { - permissions_dir |= mode_world_write; - } - else if(in_permissions_dir && args[i] == "WORLD_EXECUTE") - { - permissions_dir |= mode_world_execute; + if(!installer.CheckPermissions(args[i], permissions_file)) + { + return false; + } } - else if(in_permissions_dir && args[i] == "SETUID") + else if(doing_permissions_dir) { - permissions_dir |= mode_setuid; + if(!installer.CheckPermissions(args[i], permissions_dir)) + { + return false; + } } - else if(in_permissions_dir && args[i] == "SETGID") + else if(doing_permissions_match) { - permissions_dir |= mode_setgid; + if(!installer.CheckPermissions( + args[i], current_match_rule->Properties.Permissions)) + { + return false; + } } else { @@ -746,7 +955,6 @@ bool cmFileCommand::HandleInstallCommand( } } - int destDirLength = 0; if ( destdir && *destdir ) { std::string sdestdir = destdir; @@ -800,7 +1008,7 @@ bool cmFileCommand::HandleInstallCommand( } } destination = sdestdir + (destination.c_str() + skip); - destDirLength = int(sdestdir.size()); + installer.DestDirLength = int(sdestdir.size()); } if ( files.size() == 0 ) @@ -871,7 +1079,7 @@ bool cmFileCommand::HandleInstallCommand( // If file permissions were not specified set default permissions // for this target type. - if(!use_given_permissions_file) + if(!use_given_permissions_file && !use_source_permissions) { switch(itype) { @@ -910,7 +1118,7 @@ bool cmFileCommand::HandleInstallCommand( } // If directory permissions were not specified set default permissions. - if(!use_given_permissions_dir) + if(!use_given_permissions_dir && !use_source_permissions) { // Use read/write/executable permissions. permissions_dir = 0; @@ -923,20 +1131,10 @@ bool cmFileCommand::HandleInstallCommand( permissions_dir |= mode_world_execute; } - // Construct a file installer object. - cmFileInstaller installer(this, this->Makefile); + // Set the installer permissions. installer.FilePermissions = permissions_file; installer.DirPermissions = permissions_dir; - // Get the current manifest. - const char* manifest_files = - this->Makefile->GetDefinition("CMAKE_INSTALL_MANIFEST_FILES"); - std::string smanifest_files; - if ( manifest_files ) - { - smanifest_files = manifest_files; - } - // Check whether files should be copied always or only if they have // changed. bool copy_always = @@ -1008,8 +1206,7 @@ bool cmFileCommand::HandleInstallCommand( this->SetError(errstring.c_str()); return false; } - smanifest_files += ";"; - smanifest_files += libname.substr(destDirLength);; + installer.ManifestAppend(libname); if ( toFile != soname ) { if ( !cmSystemTools::CreateSymlink(fromName.c_str(), @@ -1020,8 +1217,7 @@ bool cmFileCommand::HandleInstallCommand( this->SetError(errstring.c_str()); return false; } - smanifest_files += ";"; - smanifest_files += soname.substr(destDirLength); + installer.ManifestAppend(soname); } } } @@ -1056,8 +1252,7 @@ bool cmFileCommand::HandleInstallCommand( this->SetError(errstring.c_str()); return false; } - smanifest_files += ";"; - smanifest_files += exename.substr(destDirLength); + installer.ManifestAppend(exename); } } break; @@ -1077,8 +1272,7 @@ bool cmFileCommand::HandleInstallCommand( { // Try installing this directory. if(!installer.InstallDirectory(fromFile.c_str(), toFile.c_str(), - copy_always, smanifest_files, - destDirLength)) + copy_always)) { return false; } @@ -1087,7 +1281,7 @@ bool cmFileCommand::HandleInstallCommand( { // Install this file. if(!installer.InstallFile(fromFile.c_str(), toFile.c_str(), - copy_always, false)) + copy_always)) { return false; } @@ -1109,10 +1303,6 @@ bool cmFileCommand::HandleInstallCommand( } } #endif - - // Add the file to the manifest. - smanifest_files += ";"; - smanifest_files += toFile.substr(destDirLength); } else if(!optional) { @@ -1125,10 +1315,6 @@ bool cmFileCommand::HandleInstallCommand( } } - // Save the updated install manifest. - this->Makefile->AddDefinition("CMAKE_INSTALL_MANIFEST_FILES", - smanifest_files.c_str()); - return true; } diff --git a/Source/cmInstallCommand.cxx b/Source/cmInstallCommand.cxx index 833beae..0e05ab7 100644 --- a/Source/cmInstallCommand.cxx +++ b/Source/cmInstallCommand.cxx @@ -21,6 +21,8 @@ #include "cmInstallScriptGenerator.h" #include "cmInstallTargetGenerator.h" +#include + // cmInstallCommand bool cmInstallCommand::InitialPass(std::vector const& args) { @@ -686,33 +688,117 @@ cmInstallCommand::HandleDirectoryMode(std::vector const& args) { bool doing_dirs = true; bool doing_destination = false; + bool doing_pattern = false; + bool doing_regex = false; bool doing_permissions_file = false; bool doing_permissions_dir = false; + bool doing_permissions_match = false; bool doing_configurations = false; bool doing_component = false; + bool in_match_mode = false; std::vector dirs; const char* destination = 0; std::string permissions_file; std::string permissions_dir; std::vector configurations; std::string component; + std::string literal_args; for(unsigned int i=1; i < args.size(); ++i) { if(args[i] == "DESTINATION") { + if(in_match_mode) + { + cmOStringStream e; + e << args[0] << " does not allow \"" + << args[i] << "\" after PATTERN or REGEX."; + this->SetError(e.str().c_str()); + return false; + } + // Switch to setting the destination property. doing_dirs = false; doing_destination = true; + doing_pattern = false; + doing_regex = false; + doing_permissions_file = false; + doing_permissions_dir = false; + doing_configurations = false; + doing_component = false; + } + else if(args[i] == "PATTERN") + { + // Switch to a new pattern match rule. + doing_dirs = false; + doing_destination = false; + doing_pattern = true; + doing_regex = false; + doing_permissions_file = false; + doing_permissions_dir = false; + doing_permissions_match = false; + doing_configurations = false; + doing_component = false; + in_match_mode = true; + } + else if(args[i] == "REGEX") + { + // Switch to a new regex match rule. + doing_dirs = false; + doing_destination = false; + doing_pattern = false; + doing_regex = true; doing_permissions_file = false; doing_permissions_dir = false; + doing_permissions_match = false; doing_configurations = false; doing_component = false; + in_match_mode = true; + } + else if(args[i] == "EXCLUDE") + { + // Add this property to the current match rule. + if(!in_match_mode || doing_pattern || doing_regex) + { + cmOStringStream e; + e << args[0] << " does not allow \"" + << args[i] << "\" before a PATTERN or REGEX is given."; + this->SetError(e.str().c_str()); + return false; + } + literal_args += " EXCLUDE"; + doing_permissions_match = false; + } + else if(args[i] == "PERMISSIONS") + { + if(!in_match_mode) + { + cmOStringStream e; + e << args[0] << " does not allow \"" + << args[i] << "\" before a PATTERN or REGEX is given."; + this->SetError(e.str().c_str()); + return false; + } + + // Switch to setting the current match permissions property. + literal_args += " PERMISSIONS"; + doing_permissions_match = true; } else if(args[i] == "FILE_PERMISSIONS") { + if(in_match_mode) + { + cmOStringStream e; + e << args[0] << " does not allow \"" + << args[i] << "\" after PATTERN or REGEX."; + this->SetError(e.str().c_str()); + return false; + } + // Switch to setting the file permissions property. doing_dirs = false; doing_destination = false; + doing_pattern = false; + doing_regex = false; doing_permissions_file = true; doing_permissions_dir = false; doing_configurations = false; @@ -720,19 +806,63 @@ cmInstallCommand::HandleDirectoryMode(std::vector const& args) } else if(args[i] == "DIRECTORY_PERMISSIONS") { + if(in_match_mode) + { + cmOStringStream e; + e << args[0] << " does not allow \"" + << args[i] << "\" after PATTERN or REGEX."; + this->SetError(e.str().c_str()); + return false; + } + // Switch to setting the directory permissions property. doing_dirs = false; doing_destination = false; + doing_pattern = false; + doing_regex = false; doing_permissions_file = false; doing_permissions_dir = true; doing_configurations = false; doing_component = false; } + else if(args[i] == "USE_SOURCE_PERMISSIONS") + { + if(in_match_mode) + { + cmOStringStream e; + e << args[0] << " does not allow \"" + << args[i] << "\" after PATTERN or REGEX."; + this->SetError(e.str().c_str()); + return false; + } + + // Add this option literally. + doing_dirs = false; + doing_destination = false; + doing_pattern = false; + doing_regex = false; + doing_permissions_file = false; + doing_permissions_dir = false; + doing_configurations = false; + doing_component = false; + literal_args += " USE_SOURCE_PERMISSIONS"; + } else if(args[i] == "CONFIGURATIONS") { + if(in_match_mode) + { + cmOStringStream e; + e << args[0] << " does not allow \"" + << args[i] << "\" after PATTERN or REGEX."; + this->SetError(e.str().c_str()); + return false; + } + // Switch to setting the configurations property. doing_dirs = false; doing_destination = false; + doing_pattern = false; + doing_regex = false; doing_permissions_file = false; doing_permissions_dir = false; doing_configurations = true; @@ -740,9 +870,20 @@ cmInstallCommand::HandleDirectoryMode(std::vector const& args) } else if(args[i] == "COMPONENT") { + if(in_match_mode) + { + cmOStringStream e; + e << args[0] << " does not allow \"" + << args[i] << "\" after PATTERN or REGEX."; + this->SetError(e.str().c_str()); + return false; + } + // Switch to setting the component property. doing_dirs = false; doing_destination = false; + doing_pattern = false; + doing_regex = false; doing_permissions_file = false; doing_permissions_dir = false; doing_configurations = false; @@ -781,6 +922,28 @@ cmInstallCommand::HandleDirectoryMode(std::vector const& args) destination = args[i].c_str(); doing_destination = false; } + else if(doing_pattern) + { + // Convert the pattern to a regular expression. Require a + // leading slash and trailing end-of-string in the matched + // string to make sure the pattern matches only whole file + // names. + literal_args += " REGEX \"/"; + std::string regex = cmsys::Glob::PatternToRegex(args[i], false); + cmSystemTools::ReplaceString(regex, "\\", "\\\\"); + literal_args += regex; + literal_args += "$\""; + doing_pattern = false; + } + else if(doing_regex) + { + literal_args += " REGEX \""; + std::string regex = args[i]; + cmSystemTools::ReplaceString(regex, "\\", "\\\\"); + literal_args += regex; + literal_args += "\""; + doing_regex = false; + } else if(doing_component) { component = args[i]; @@ -810,6 +973,18 @@ cmInstallCommand::HandleDirectoryMode(std::vector const& args) return false; } } + else if(doing_permissions_match) + { + // Check the requested permission. + if(!this->CheckPermissions(args[i], literal_args)) + { + cmOStringStream e; + e << args[0] << " given invalid permission \"" + << args[i] << "\"."; + this->SetError(e.str().c_str()); + return false; + } + } else { // Unknown argument. @@ -844,7 +1019,8 @@ cmInstallCommand::HandleDirectoryMode(std::vector const& args) permissions_file.c_str(), permissions_dir.c_str(), configurations, - component.c_str())); + component.c_str(), + literal_args.c_str())); // Tell the global generator about any installation component names // specified. diff --git a/Source/cmInstallCommand.h b/Source/cmInstallCommand.h index 7bf77df..0607d16 100644 --- a/Source/cmInstallCommand.h +++ b/Source/cmInstallCommand.h @@ -165,6 +165,55 @@ public: "such as shell scripts. Use the TARGETS form to install targets " "built within the project." "\n" + "The DIRECTORY signature:\n" + " INSTALL(DIRECTORY dirs... DESTINATION \n" + " [FILE_PERMISSIONS permissions...]\n" + " [DIRECTORY_PERMISSIONS permissions...]\n" + " [USE_SOURCE_PERMISSIONS]\n" + " [CONFIGURATIONS [Debug|Release|...]]\n" + " [COMPONENT ]\n" + " [[PATTERN | REGEX ]\n" + " [EXCLUDE] [PERMISSIONS permissions...]] [...])\n" + "The DIRECTORY form installs contents of one or more directories " + "to a given destination. " + "The directory structure is copied verbatim to the destination. " + "The last component of each directory name is appended to the " + "destination directory but a trailing slash may be used to " + "avoid this because it leaves the last component empty. " + "Directory names given as relative paths are interpreted with " + "respect to the current source directory. " + "The FILE_PERMISSIONS and DIRECTORY_PERMISSIONS options specify " + "permissions given to files and directories in the destination. " + "If USE_SOURCE_PERMISSIONS is specified and FILE_PERMISSIONS is not, " + "file permissions will be copied from the source directory structure. " + "If no permissions are specified files will be given the default " + "permissions specified in the FILES form of the command, and the " + "directories will be given the default permissions specified in the " + "PROGRAMS form of the command. " + "The PATTERN and REGEX options specify a globbing pattern or regular " + "expression to match directories or files encountered during traversal " + "of an input directory. The full path to an input file or directory " + "(with forward slashes) is matched against the expression. " + "A PATTERN will match only complete file names: the portion of the full " + "path matching the pattern must occur at the end of the file name and " + "be preceded by a slash. " + "A REGEX will match any portion of the full path but it may use " + "'/' and '$' to simulate the PATTERN behavior. " + "Options following one of these matching expressions " + "are applied only to files or directories matching them. The EXCLUDE " + "option will skip the matched file or directory. The PERMISSIONS " + "option overrides the permissions setting for the matched file. " + "For example the code\n" + " INSTALL(DIRECTORY icons scripts/ DESTINATION share/myproj\n" + " PATTERN \"CVS\" EXCLUDE\n" + " PATTERN \"scripts/*\"\n" + " PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ\n" + " GROUP_EXECUTE GROUP_READ)\n" + "will install the icons directory to share/myproj/icons and the " + "scripts directory to share/myproj. The icons will get default file " + "permissions, the scripts will be given specific permissions, and " + "any CVS directories will be excluded." + "\n" "The SCRIPT and CODE signature:\n" " INSTALL([[SCRIPT ] [CODE ]] [...])\n" "The SCRIPT form will invoke the given CMake script files during " diff --git a/Source/cmInstallDirectoryGenerator.cxx b/Source/cmInstallDirectoryGenerator.cxx index 9ff09a6..aaf8235 100644 --- a/Source/cmInstallDirectoryGenerator.cxx +++ b/Source/cmInstallDirectoryGenerator.cxx @@ -25,10 +25,12 @@ cmInstallDirectoryGenerator const char* file_permissions, const char* dir_permissions, std::vector const& configurations, - const char* component): + const char* component, + const char* literal_args): Directories(dirs), Destination(dest), FilePermissions(file_permissions), DirPermissions(dir_permissions), - Configurations(configurations), Component(component) + Configurations(configurations), Component(component), + LiteralArguments(literal_args) { } @@ -54,6 +56,6 @@ void cmInstallDirectoryGenerator::GenerateScript(std::ostream& os) this->FilePermissions.c_str(), this->DirPermissions.c_str(), this->Configurations, this->Component.c_str(), - no_rename); + no_rename, this->LiteralArguments.c_str()); } } diff --git a/Source/cmInstallDirectoryGenerator.h b/Source/cmInstallDirectoryGenerator.h index 6b36434..296130e 100644 --- a/Source/cmInstallDirectoryGenerator.h +++ b/Source/cmInstallDirectoryGenerator.h @@ -30,7 +30,8 @@ public: const char* file_permissions, const char* dir_permissions, std::vector const& configurations, - const char* component); + const char* component, + const char* literal_args); virtual ~cmInstallDirectoryGenerator(); protected: @@ -41,6 +42,7 @@ protected: std::string DirPermissions; std::vector Configurations; std::string Component; + std::string LiteralArguments; }; #endif diff --git a/Source/cmInstallGenerator.cxx b/Source/cmInstallGenerator.cxx index 034410b..275eb11 100644 --- a/Source/cmInstallGenerator.cxx +++ b/Source/cmInstallGenerator.cxx @@ -59,7 +59,8 @@ void cmInstallGenerator const char* permissions_dir /* = 0 */, std::vector const& configurations, const char* component /* = 0 */, - const char* rename /* = 0 */ + const char* rename /* = 0 */, + const char* literal_args /* = 0 */ ) { // Use the FILE command to install the file. @@ -109,5 +110,10 @@ void cmInstallGenerator { os << " COMPONENTS \"" << component << "\""; } - os << " FILES \"" << file << "\")\n"; + os << " FILES \"" << file << "\""; + if(literal_args && *literal_args) + { + os << literal_args; + } + os << ")\n"; } diff --git a/Source/cmInstallGenerator.h b/Source/cmInstallGenerator.h index df165c0..94c0fbd 100644 --- a/Source/cmInstallGenerator.h +++ b/Source/cmInstallGenerator.h @@ -43,7 +43,8 @@ public: std::vector const& configurations = std::vector(), const char* component = 0, - const char* rename = 0 + const char* rename = 0, + const char* literal_args = 0 ); protected: diff --git a/Source/cmInstallTargetGenerator.cxx b/Source/cmInstallTargetGenerator.cxx index a15ce3c..1e99cd9 100644 --- a/Source/cmInstallTargetGenerator.cxx +++ b/Source/cmInstallTargetGenerator.cxx @@ -72,6 +72,7 @@ void cmInstallTargetGenerator::GenerateScript(std::ostream& os) std::string destination = this->Destination; // Setup special properties for some target types. + std::string literal_args; std::string props; const char* properties = 0; cmTarget::TargetType type = this->Target->GetType(); @@ -140,6 +141,7 @@ void cmInstallTargetGenerator::GenerateScript(std::ostream& os) false, false); fromFile += ".app"; type = cmTarget::INSTALL_DIRECTORY; + literal_args += " USE_SOURCE_PERMISSIONS"; } } break; @@ -159,11 +161,13 @@ void cmInstallTargetGenerator::GenerateScript(std::ostream& os) // Write code to install the target file. const char* no_dir_permissions = 0; + const char* no_rename = 0; this->AddInstallRule(os, destination.c_str(), type, fromFile.c_str(), this->ImportLibrary, properties, this->FilePermissions.c_str(), no_dir_permissions, this->Configurations, - this->Component.c_str()); + this->Component.c_str(), + no_rename, literal_args.c_str()); // Fix the install_name settings in installed binaries. if(type == cmTarget::SHARED_LIBRARY || diff --git a/Tests/SimpleInstall/CMakeLists.txt b/Tests/SimpleInstall/CMakeLists.txt index 4df7f77..f543d43 100644 --- a/Tests/SimpleInstall/CMakeLists.txt +++ b/Tests/SimpleInstall/CMakeLists.txt @@ -80,6 +80,22 @@ IF(STAGE2) MESSAGE(FATAL_ERROR "Release-configuration file installed for Debug!") ENDIF(EXISTS "${CMAKE_INSTALL_PREFIX}/MyTest/include/Debug/lib1release.h") + # Check for failure of directory installation. + IF(NOT EXISTS "${CMAKE_INSTALL_PREFIX}/MyTest/share/TestSubDir/TSD.h") + MESSAGE(FATAL_ERROR "Directory installation did not install TSD.h") + ENDIF(NOT EXISTS "${CMAKE_INSTALL_PREFIX}/MyTest/share/TestSubDir/TSD.h") + EXECUTE_PROCESS( + COMMAND "${CMAKE_INSTALL_PREFIX}/MyTest/share/sample_script" + RESULT_VARIABLE SAMPLE_SCRIPT_RESULT + OUTPUT_VARIABLE SAMPLE_SCRIPT_OUTPUT + ) + IF(NOT "${SAMPLE_SCRIPT_RESULT}" MATCHES "^0$") + MESSAGE(FATAL_ERROR "Sample script failed: [${SAMPLE_SCRIPT_RESULT}]") + ENDIF(NOT "${SAMPLE_SCRIPT_RESULT}" MATCHES "^0$") + IF(NOT "${SAMPLE_SCRIPT_OUTPUT}" MATCHES "Sample Script Output") + MESSAGE(FATAL_ERROR "Bad sample script output: [${SAMPLE_SCRIPT_OUTPUT}]") + ENDIF(NOT "${SAMPLE_SCRIPT_OUTPUT}" MATCHES "Sample Script Output") + # Make sure the test executable can run from the install tree. SET_TARGET_PROPERTIES(SimpleInstallS2 PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/MyTest/lib) @@ -138,6 +154,13 @@ ELSE(STAGE2) DESTINATION MyTest/include/Debug ) + # Test directory installation. + INSTALL( + DIRECTORY TestSubDir scripts/ DESTINATION MyTest/share + PATTERN "CVS" EXCLUDE + PATTERN "scripts/*" PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ + ) + # Test user-specified install scripts. INSTALL( SCRIPT InstallScript1.cmake diff --git a/Tests/SimpleInstall/scripts/.gitattributes b/Tests/SimpleInstall/scripts/.gitattributes new file mode 100644 index 0000000..5e3db2f --- /dev/null +++ b/Tests/SimpleInstall/scripts/.gitattributes @@ -0,0 +1 @@ +sample_script crlf=input diff --git a/Tests/SimpleInstall/scripts/sample_script b/Tests/SimpleInstall/scripts/sample_script new file mode 100755 index 0000000..81f9f53 --- /dev/null +++ b/Tests/SimpleInstall/scripts/sample_script @@ -0,0 +1,2 @@ +#!/bin/sh +echo "Sample Script Output" diff --git a/Tests/SimpleInstall/scripts/sample_script.bat b/Tests/SimpleInstall/scripts/sample_script.bat new file mode 100755 index 0000000..64a77b5 --- /dev/null +++ b/Tests/SimpleInstall/scripts/sample_script.bat @@ -0,0 +1 @@ +@echo Sample Script Output diff --git a/Tests/SimpleInstallS2/CMakeLists.txt b/Tests/SimpleInstallS2/CMakeLists.txt index 4df7f77..f543d43 100644 --- a/Tests/SimpleInstallS2/CMakeLists.txt +++ b/Tests/SimpleInstallS2/CMakeLists.txt @@ -80,6 +80,22 @@ IF(STAGE2) MESSAGE(FATAL_ERROR "Release-configuration file installed for Debug!") ENDIF(EXISTS "${CMAKE_INSTALL_PREFIX}/MyTest/include/Debug/lib1release.h") + # Check for failure of directory installation. + IF(NOT EXISTS "${CMAKE_INSTALL_PREFIX}/MyTest/share/TestSubDir/TSD.h") + MESSAGE(FATAL_ERROR "Directory installation did not install TSD.h") + ENDIF(NOT EXISTS "${CMAKE_INSTALL_PREFIX}/MyTest/share/TestSubDir/TSD.h") + EXECUTE_PROCESS( + COMMAND "${CMAKE_INSTALL_PREFIX}/MyTest/share/sample_script" + RESULT_VARIABLE SAMPLE_SCRIPT_RESULT + OUTPUT_VARIABLE SAMPLE_SCRIPT_OUTPUT + ) + IF(NOT "${SAMPLE_SCRIPT_RESULT}" MATCHES "^0$") + MESSAGE(FATAL_ERROR "Sample script failed: [${SAMPLE_SCRIPT_RESULT}]") + ENDIF(NOT "${SAMPLE_SCRIPT_RESULT}" MATCHES "^0$") + IF(NOT "${SAMPLE_SCRIPT_OUTPUT}" MATCHES "Sample Script Output") + MESSAGE(FATAL_ERROR "Bad sample script output: [${SAMPLE_SCRIPT_OUTPUT}]") + ENDIF(NOT "${SAMPLE_SCRIPT_OUTPUT}" MATCHES "Sample Script Output") + # Make sure the test executable can run from the install tree. SET_TARGET_PROPERTIES(SimpleInstallS2 PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/MyTest/lib) @@ -138,6 +154,13 @@ ELSE(STAGE2) DESTINATION MyTest/include/Debug ) + # Test directory installation. + INSTALL( + DIRECTORY TestSubDir scripts/ DESTINATION MyTest/share + PATTERN "CVS" EXCLUDE + PATTERN "scripts/*" PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ + ) + # Test user-specified install scripts. INSTALL( SCRIPT InstallScript1.cmake diff --git a/Tests/SimpleInstallS2/scripts/.gitattributes b/Tests/SimpleInstallS2/scripts/.gitattributes new file mode 100644 index 0000000..5e3db2f --- /dev/null +++ b/Tests/SimpleInstallS2/scripts/.gitattributes @@ -0,0 +1 @@ +sample_script crlf=input diff --git a/Tests/SimpleInstallS2/scripts/sample_script b/Tests/SimpleInstallS2/scripts/sample_script new file mode 100755 index 0000000..81f9f53 --- /dev/null +++ b/Tests/SimpleInstallS2/scripts/sample_script @@ -0,0 +1,2 @@ +#!/bin/sh +echo "Sample Script Output" diff --git a/Tests/SimpleInstallS2/scripts/sample_script.bat b/Tests/SimpleInstallS2/scripts/sample_script.bat new file mode 100755 index 0000000..64a77b5 --- /dev/null +++ b/Tests/SimpleInstallS2/scripts/sample_script.bat @@ -0,0 +1 @@ +@echo Sample Script Output -- cgit v0.12