1<?php 2 3/* 4 * $Id: 6efb50d5b7cb94f2f22db6e876010e718aa25b22 $ 5 * 6 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 7 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 8 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 9 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 10 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 11 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 12 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 13 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 14 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 15 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 16 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 17 * 18 * This software consists of voluntary contributions made by many individuals 19 * and is licensed under the LGPL. For more information please see 20 * <http://phing.info>. 21 */ 22 23require_once "phing/Task.php"; 24 25/** 26 * Generates symlinks based on a target / link combination. 27 * Can also symlink contents of a directory, individually 28 * 29 * Single target symlink example: 30 * <code> 31 * <symlink target="/some/shared/file" link="${project.basedir}/htdocs/my_file" /> 32 * </code> 33 * 34 * Symlink entire contents of directory 35 * 36 * This will go through the contents of "/my/shared/library/*" 37 * and create a symlink for each entry into ${project.basedir}/library/ 38 * <code> 39 * <symlink link="${project.basedir}/library"> 40 * <fileset dir="/my/shared/library"> 41 * <include name="*" /> 42 * </fileset> 43 * </symlink> 44 * </code> 45 * 46 * @author Andrei Serdeliuc <andrei@serdeliuc.ro> 47 * @extends Task 48 * @version $ID$ 49 * @package phing.tasks.ext 50 */ 51class SymlinkTask extends Task 52{ 53 /** 54 * What we're symlinking from 55 * 56 * (default value: null) 57 * 58 * @var string 59 * @access private 60 */ 61 private $_target = null; 62 63 /** 64 * Symlink location 65 * 66 * (default value: null) 67 * 68 * @var string 69 * @access private 70 */ 71 private $_link = null; 72 73 /** 74 * Collection of filesets 75 * Used when linking contents of a directory 76 * 77 * (default value: array()) 78 * 79 * @var array 80 * @access private 81 */ 82 private $_filesets = array(); 83 84 /** 85 * Whether to override the symlink if it exists but points 86 * to a different location 87 * 88 * (default value: false) 89 * 90 * @var boolean 91 * @access private 92 */ 93 private $_overwrite = false; 94 95 /** 96 * setter for _target 97 * 98 * @access public 99 * @param string $target 100 * @return void 101 */ 102 public function setTarget($target) 103 { 104 $this->_target = $target; 105 } 106 107 /** 108 * setter for _link 109 * 110 * @access public 111 * @param string $link 112 * @return void 113 */ 114 public function setLink($link) 115 { 116 $this->_link = $link; 117 } 118 119 /** 120 * creator for _filesets 121 * 122 * @access public 123 * @return FileSet 124 */ 125 public function createFileset() 126 { 127 $num = array_push($this->_filesets, new FileSet()); 128 return $this->_filesets[$num-1]; 129 } 130 131 /** 132 * setter for _overwrite 133 * 134 * @access public 135 * @param boolean $overwrite 136 * @return void 137 */ 138 public function setOverwrite($overwrite) 139 { 140 $this->_overwrite = $overwrite; 141 } 142 143 /** 144 * getter for _target 145 * 146 * @access public 147 * @return string 148 */ 149 public function getTarget() 150 { 151 if($this->_target === null) { 152 throw new BuildException('Target not set'); 153 } 154 155 return $this->_target; 156 } 157 158 /** 159 * getter for _link 160 * 161 * @access public 162 * @return string 163 */ 164 public function getLink() 165 { 166 if($this->_link === null) { 167 throw new BuildException('Link not set'); 168 } 169 170 return $this->_link; 171 } 172 173 /** 174 * getter for _filesets 175 * 176 * @access public 177 * @return array 178 */ 179 public function getFilesets() 180 { 181 return $this->_filesets; 182 } 183 184 /** 185 * getter for _overwrite 186 * 187 * @access public 188 * @return boolean 189 */ 190 public function getOverwrite() 191 { 192 return $this->_overwrite; 193 } 194 195 /** 196 * Generates an array of directories / files to be linked 197 * If _filesets is empty, returns getTarget() 198 * 199 * @access protected 200 * @return array|string 201 */ 202 protected function getMap() 203 { 204 $fileSets = $this->getFilesets(); 205 206 // No filesets set 207 // We're assuming single file / directory 208 if(empty($fileSets)) { 209 return $this->getTarget(); 210 } 211 212 $targets = array(); 213 214 foreach($fileSets as $fs) { 215 if(!($fs instanceof FileSet)) { 216 continue; 217 } 218 219 // We need a directory to store the links 220 if(!is_dir($this->getLink())) { 221 throw new BuildException('Link must be an existing directory when using fileset'); 222 } 223 224 $fromDir = $fs->getDir($this->getProject())->getAbsolutePath(); 225 226 if(!is_dir($fromDir)) { 227 $this->log('Directory doesn\'t exist: ' . $fromDir, Project::MSG_WARN); 228 continue; 229 } 230 231 $fsTargets = array(); 232 233 $ds = $fs->getDirectoryScanner($this->getProject()); 234 235 $fsTargets = array_merge( 236 $fsTargets, 237 $ds->getIncludedDirectories(), 238 $ds->getIncludedFiles() 239 ); 240 241 // Add each target to the map 242 foreach($fsTargets as $target) { 243 if(!empty($target)) { 244 $targets[$target] = $fromDir . DIRECTORY_SEPARATOR . $target; 245 } 246 } 247 } 248 249 return $targets; 250 } 251 252 /** 253 * Main entry point for task 254 * 255 * @access public 256 * @return bool 257 */ 258 public function main() 259 { 260 $map = $this->getMap(); 261 262 // Single file symlink 263 if(is_string($map)) { 264 return $this->symlink($map, $this->getLink()); 265 } 266 267 // Multiple symlinks 268 foreach($map as $name => $targetPath) { 269 $this->symlink($targetPath, $this->getLink() . DIRECTORY_SEPARATOR . $name); 270 } 271 272 return true; 273 } 274 275 /** 276 * Create the actual link 277 * 278 * @access protected 279 * @param string $target 280 * @param string $link 281 * @return bool 282 */ 283 protected function symlink($target, $link) 284 { 285 $fs = FileSystem::getFileSystem(); 286 287 if (is_link($link) && readlink($link) == $target) { 288 $this->log('Link exists: ' . $link, Project::MSG_INFO); 289 return true; 290 } elseif (file_exists($link)) { 291 if (!$this->getOverwrite()) { 292 $this->log('Not overwriting existing link ' . $link, Project::MSG_ERR); 293 return false; 294 } 295 296 if (is_link($link) || is_file($link)) { 297 $fs->unlink($link); 298 $this->log('Link removed: ' . $link, Project::MSG_INFO); 299 } else { 300 $fs->rmdir($link, true); 301 $this->log('Directory removed: ' . $link, Project::MSG_INFO); 302 } 303 } 304 305 $this->log('Linking: ' . $target . ' to ' . $link, Project::MSG_INFO); 306 307 return $fs->symlink($target, $link); 308 } 309} 310