1 /+ dub.sdl: 2 name "configure" 3 targetPath "bin" 4 +/ 5 /** 6 * This file implements a configuration script that will setup the correct flags 7 * to link with libclang. 8 * 9 * The script will try to automatically detect the location of libclang by 10 * searching through a number of preset library search paths for different 11 * platforms. 12 * 13 * If the script fails to find libclang or fails to find the correct version, 14 * this script provides a flag,`--llvm-config`, which can be used by manually 15 * executing this script (`./configure`) and specifying the path to the LLVM 16 * configuration binary, `llvm-config`. The LLVM configuration binary will then 17 * be used to find the location of libclang. 18 * 19 * The result of invoking this configuration script is a file, 20 * `linker_flags.txt`, which will be created. This file contains the necessary 21 * linker flags which will be read by the linker when building DStep. 22 * 23 * This script is only intended to be used on the Posix platforms. 24 */ 25 module configure; 26 27 import std.algorithm; 28 import std.array; 29 import std.conv; 30 import std.exception; 31 import std.file; 32 import std.format; 33 import std.getopt; 34 import std.path; 35 import std.process; 36 import std.range; 37 import std..string; 38 import std.uni; 39 import std.traits; 40 41 version (Posix) 42 { 43 version (OSX) {} 44 else version (linux) {} 45 else version (FreeBSD) {} 46 else 47 static assert("The current platform is not supported"); 48 } 49 else 50 static assert("This script should only be run on Posix platforms"); 51 52 /** 53 * The options of the application. 54 * 55 * When parsing the command line arguments, these fields will be set. 56 */ 57 struct Options 58 { 59 /// Print extra information. 60 bool verbose = true; 61 62 /// Indicates if help/usage information was requested. 63 bool help = false; 64 65 /// The specified path to the LLVM/Clang library path. 66 string llvmLibPath; 67 68 /** 69 * The specified path to the location of additional libraries, like 70 * `ncurses` or `tinfo`, that needs to linked when linking libclang 71 * statically. 72 */ 73 string additionalLibPath; 74 75 /// Indicates if libclang should be statically or dynamically linked. 76 bool staticallyLinkClang = false; 77 78 /** 79 * Indicates if the whole binary, including the C standard library, should 80 * be statically linked. 81 */ 82 bool staticallyLinkBinary = false; 83 } 84 85 /// This struct contains the name and filename of a library. 86 struct LibraryName 87 { 88 /** 89 * The name of the library. 90 * 91 * Used in error message and similar. 92 */ 93 string name; 94 95 /// The filename of the library. 96 string filename; 97 } 98 99 /// Default configuration and paths. 100 struct DefaultConfig 101 { 102 static: 103 104 version (D_Ddoc) 105 { 106 /// The name of the Clang dynamic library. 107 enum clangLib = ""; 108 109 /** 110 * A list of default paths where to look for the LLVM and Clang 111 * libraries. 112 */ 113 immutable string[] llvmLibPaths = []; 114 115 /** 116 * A list of default paths where to look for additional libraries. 117 * 118 * Thes are libraries that are not part of LLVM or Clang which are used 119 * when statically linking libclang. 120 */ 121 immutable string[] additionalLibPaths = []; 122 123 /** 124 * The name of the additional static library, like `ncurses` or `tinfo`. 125 * 126 * Used when statically linking libclang. 127 */ 128 enum additionalLib = LibraryName(); 129 130 /** 131 * The name of the C++ standard library. 132 * 133 * Used when statically linking libclang. 134 */ 135 enum cppLib = "c++"; 136 } 137 138 else version (OSX) 139 { 140 enum clangLib = "libclang.dylib"; 141 142 enum standardPaths = [ 143 "/usr/local/lib", 144 "/usr/lib" 145 ]; 146 147 immutable llvmLibPaths = [ 148 "/opt/local/libexec/llvm-4.0/lib", // MacPorts 149 "/usr/local/opt/llvm40/lib", // Homebrew 150 "/opt/local/libexec/llvm-3.9/lib", // MacPorts 151 "/usr/local/opt/llvm39/lib", // Homebrew 152 "/opt/local/libexec/llvm-3.8/lib", // MacPorts 153 "/usr/local/opt/llvm38/lib", // Homebrew 154 "/opt/local/libexec/llvm-3.7/lib", // MacPorts 155 "/usr/local/opt/llvm37/lib" // Homebrew 156 ] ~ standardPaths; 157 158 immutable additionalLibPaths = [ 159 "/opt/local/lib" 160 ] ~ standardPaths; 161 162 enum additionalLib = LibraryName("ncurses", "libncurses.a"); 163 enum cppLib = "c++"; 164 } 165 166 else version (linux) 167 { 168 enum clangLib = "libclang.so"; 169 170 enum standardPaths = [ 171 "/usr/lib", 172 "/usr/local/lib", 173 "/usr/lib/x86_64-linux-gnu", // Debian 174 "/usr/lib64", // Fedora 175 "/usr/lib32" // Fedora 176 ]; 177 178 immutable llvmLibPaths = [ 179 "/usr/lib/llvm-4.0/lib", // Debian 180 "/usr/lib/llvm-3.9/lib", // Debian 181 "/usr/lib/llvm-3.8/lib", // Debian 182 "/usr/lib/llvm-3.7/lib", // Debian 183 "/usr/lib64/llvm", // CentOS 184 "/usr/lib32/llvm" // CentOS 185 ] ~ standardPaths; 186 187 immutable additionalLibPaths = standardPaths; 188 189 enum additionalLib = LibraryName("tinfo", "libtinfo.a"); 190 enum cppLib = "stdc++"; 191 } 192 193 else version (FreeBSD) 194 { 195 enum clangLib = "libclang.so"; 196 197 enum standardPaths = [ 198 "/usr/lib", 199 "/usr/local/lib" 200 ]; 201 202 immutable llvmLibPaths = [ 203 "/usr/lib/llvm-4.0/lib", 204 "/usr/lib/llvm-3.9/lib", 205 "/usr/lib/llvm-3.8/lib", 206 "/usr/lib/llvm-3.7/lib" 207 ] ~ standardPaths; 208 209 immutable additionalLibPaths = standardPaths; 210 211 enum ncursesLib = LibraryName("ncurses", "libncurses.a"); 212 enum cppLib = "stdc++"; 213 } 214 215 else 216 static assert(false, "Unsupported platform"); 217 218 /// The name of the LLVM configure binary. 219 enum llvmConfigExecutable = "llvm-config"; 220 } 221 222 /** 223 * This class represents a path to a file, like a library or an executable. 224 * 225 * It's the abstract base class for the `LibraryPath` and `LLVMConfigPath` 226 * subclasses. 227 */ 228 class Path 229 { 230 private 231 { 232 /** 233 * The name of the file this path represents. 234 * 235 * This is a name for the file that is used in error messages. 236 */ 237 string name; 238 239 /** 240 * A set of standard paths to which to search for the file this path 241 * represents. 242 */ 243 const(string)[] standardPaths; 244 245 /** 246 * The custom path that was specified when invoking this configuration 247 * script, or `null` if no custom path was specified. 248 */ 249 string specifiedPath; 250 251 /// The actual file to look for in `standardPaths` and `specifiedPath`. 252 string fileToCheck; 253 254 /// Local cache for the full path to the file. 255 string path_; 256 } 257 258 alias path this; 259 260 /** 261 * Constructs a new instance of this class. 262 * 263 * Params: 264 * name = the name of the file this path represents 265 * 266 * standardPaths = a set of standard paths to which to search for the file 267 * this path represents 268 * 269 * specifiedPath = the custom path that was specified when invoking this 270 * configuration script, or `null` if no custom path was specified 271 * 272 * fileToCheck = the actual file to look for in `standardPaths` and 273 * `specifiedPath` 274 */ 275 this(string name, const(string)[] standardPaths, 276 string specifiedPath, string fileToCheck) 277 { 278 this.name = name; 279 this.standardPaths = standardPaths; 280 this.specifiedPath = specifiedPath; 281 this.fileToCheck = fileToCheck; 282 } 283 284 /** 285 * Returns the full path to the file this path represents as a string. 286 * 287 * If `specifiedPath` is non-empty, `fileToCheck` will be searched for in 288 * `specifiedPath`. Otherwise `fileToCheck` will be searched for in 289 * `standardPaths`. 290 * 291 * Returns: the full path to the file this path represents 292 */ 293 string path() 294 { 295 if (path_.ptr) 296 return path_; 297 298 return path_ = specifiedPath.empty ? standardPath : customPath; 299 } 300 301 override string toString() 302 { 303 return path; 304 } 305 306 /** 307 * Returns the full path of `fileToCheck` by searching in `standardPaths`. 308 * 309 * Returns: the full path of `fileToCheck` by searching in `standardPaths` 310 * 311 * Throws: an `Exception` if `fileToCheck` cannot be found in any of the 312 * paths in `standardPath` 313 */ 314 string standardPath() 315 { 316 auto errorMessage = format("Could not find %s in any of the standard " ~ 317 "paths for %s: \n%s\nPlease specify a path manually using " ~ 318 "'./configure --%s-path=<path>'.", 319 fileToCheck, name, standardPaths.join('\n'), name.toLower 320 ); 321 322 auto result = standardPaths. 323 find!(exists). 324 find!(e => e.buildPath(fileToCheck).exists); 325 326 enforce(!result.empty, errorMessage); 327 328 return result.front.absolutePath; 329 } 330 331 private: 332 333 /** 334 * Returns the full path of `fileToCheck` by searching in `specifiedPath` 335 * and the `PATH` environment variable. 336 * 337 * If `fileToCheck` cannot be found in `specifiedPath` it will search for 338 * `fileToCheck` in the `PATH` environment variable. If that fails, an 339 * exception is thrown. 340 * 341 * Returns: the full path of `fileToCheck` 342 * 343 * Throws: an `Exception` if `fileToCheck` cannot be found in 344 * `specifiedPath` or the `PATH` environment variable 345 */ 346 string customPath() 347 { 348 auto path = specifiedPath.asAbsolutePath.asNormalizedPath.to!string; 349 350 auto errorMessage = format("The specified library %s in path '%s' " ~ 351 "does not exist.", name, path); 352 353 if (path.exists) 354 return path; 355 356 path = searchPath(specifiedPath); 357 enforce(path.exists, errorMessage); 358 359 return path; 360 } 361 } 362 363 /** 364 * This mixin template contains shared logic to generate the actual 365 * configuration. 366 */ 367 mixin template BaseConfigurator() 368 { 369 private 370 { 371 /// The name of the file where the configuration is written. 372 enum configPath = "linker_flags.txt"; 373 374 /// The options that were the result of parsing the command line flags. 375 Options options; 376 377 /// The default configuration. 378 DefaultConfig defaultConfig; 379 380 /// The LLVM/Clang library path. 381 Path llvmLibPath; 382 } 383 384 /** 385 * Initializes the receiver with the given arguments. This method acts as 386 * the shared constructor. 387 * 388 * Params: 389 * options = the options 390 * defaultConfig = the default configuration 391 */ 392 void initialize(Options options, DefaultConfig defaultConfig) 393 { 394 this.options = options; 395 this.defaultConfig = defaultConfig; 396 397 llvmLibPath = new Path( 398 "llvm", 399 defaultConfig.llvmLibPaths, 400 options.llvmLibPath, 401 defaultConfig.clangLib 402 ); 403 } 404 405 private: 406 407 /** 408 * Writes given configuration to the config file. 409 * 410 * Params: 411 * config = the configuration to write, that is, the linker flags 412 */ 413 void writeConfig(string config) 414 { 415 write(configPath, config); 416 } 417 418 /// Returns: the configuration, that is, the linker flags. 419 string config() 420 { 421 return flags.filter!(e => !e.empty).join("\n") ~ '\n'; 422 } 423 } 424 425 /** 426 * This struct contains the logic for generating the configuration for static 427 * linking. 428 */ 429 struct StaticConfigurator 430 { 431 mixin BaseConfigurator; 432 433 private 434 { 435 version (D_Ddoc) 436 { 437 /** 438 * Contains the `--start-group` flag on non-macOS platforms. 439 * 440 * Used on non-macOS platforms to group the LLVM and Clang 441 * libraries to be searched repeatedly to resolve undefined symbols. 442 */ 443 enum startGroupFlag = ""; 444 445 /** 446 * Contains the `--end-group` flag on non-macOS platforms. 447 * 448 * Used on non-macOS platforms to group the LLVM and Clang 449 * libraries to be searched repeatedly to resolve undefined symbols. 450 */ 451 enum endGroupFlag = ""; 452 } 453 454 else version (OSX) 455 { 456 enum startGroupFlag = "".only; 457 enum endGroupFlag = "".only; 458 } 459 460 else 461 { 462 enum startGroupFlag = "--start-group".only; 463 enum endGroupFlag = "-Wl,--end-group".only; 464 } 465 466 /// Local cache for the additional library path. 467 Path additionalLibPath; 468 } 469 470 /** 471 * Constructs a new instance of this struct with the given arguments. 472 * 473 * Params: 474 * options = the options 475 * defaultConfig = the default configuration 476 */ 477 this(Options options, DefaultConfig defaultConfig) 478 { 479 initialize(options, defaultConfig); 480 481 additionalLibPath = new Path(defaultConfig.additionalLib.name, 482 DefaultConfig.additionalLibPaths, 483 options.additionalLibPath, defaultConfig.additionalLib.filename); 484 } 485 486 /** 487 * Generates the actual configuration. 488 * 489 * This will locate all required libraries, build a set of linker flags and 490 * write the result to the configuration file. 491 */ 492 void generateConfig() 493 { 494 enforceLibrariesExist( 495 DefaultConfig.additionalLib.name, 496 additionalLibPath, 497 DefaultConfig.additionalLib.filename 498 ); 499 500 writeConfig(config); 501 } 502 503 private: 504 505 /// Return: a range of all the necessary linker flags. 506 auto flags() 507 { 508 return chain( 509 startGroupFlag, 510 libclangFlags, 511 llvmFlags, 512 endGroupFlag, 513 additionalLibFlags, 514 cppFlags, 515 extraFlags 516 ); 517 } 518 519 /** 520 * Returns: a range of linker flags necessary to link with the standard C++ 521 * library. 522 */ 523 auto cppFlags() 524 { 525 return format("-l%s", DefaultConfig.cppLib).only; 526 } 527 528 /** 529 * Returns: a range of linker flags necessary to link with the ncurses 530 * library. 531 */ 532 auto additionalLibFlags() 533 { 534 return additionalLibPath 535 .buildPath(DefaultConfig.additionalLib.filename) 536 .only; 537 } 538 539 /** 540 * Returns: a range of linker flags necessary to link with the LLVM 541 * libraries. 542 */ 543 auto llvmFlags() 544 { 545 return dirEntries(llvmLibPath, "libLLVM*.a", SpanMode.shallow); 546 } 547 548 /** 549 * Returns: a range of linker flags necessary to link with the Clang 550 * libraries. 551 */ 552 auto libclangFlags() 553 { 554 return dirEntries(llvmLibPath, "libclang*.a", SpanMode.shallow); 555 } 556 557 auto extraFlags() 558 { 559 return (options.staticallyLinkBinary ? "-static" : "").only; 560 } 561 } 562 563 /** 564 * This struct contains the logic for generating the configuration for dynamic 565 * linking. 566 */ 567 struct DynamicConfigurator 568 { 569 mixin BaseConfigurator; 570 571 /** 572 * Constructs a new instance of this struct with the given arguments. 573 * 574 * Params: 575 * options = the options 576 * defaultConfig = the default configuration 577 */ 578 this(Options options, DefaultConfig defaultConfig) 579 { 580 initialize(options, defaultConfig); 581 } 582 583 /** 584 * Generates the actual configuration. 585 * 586 * This will locate all required libraries, build a set of linker flags and 587 * write the result to the configuration file. 588 */ 589 void generateConfig() 590 { 591 enforceLibrariesExist("libclang", llvmLibPath, DefaultConfig.clangLib); 592 593 writeConfig(config); 594 } 595 596 private: 597 598 /// Return: a range of all the necessary linker flags. 599 auto flags() 600 { 601 return format("-L%1$s\n-lclang\n-Xlinker -rpath %1$s", llvmLibPath) 602 .only; 603 } 604 } 605 606 /// The main entry point of this script. 607 void main(string[] args) 608 { 609 auto options = parseArguments(args); 610 611 if (!options.help) 612 { 613 if (options.staticallyLinkClang) 614 StaticConfigurator(options, DefaultConfig()).generateConfig(); 615 else 616 DynamicConfigurator(options, DefaultConfig()).generateConfig(); 617 } 618 } 619 620 private: 621 622 /** 623 * Parses the command line arguments given to the application. 624 * 625 * Params: 626 * args = the command line arguments to parse 627 * 628 * Returns: the options set while parsing the arguments 629 */ 630 Options parseArguments(string[] args) 631 { 632 import std.typecons : tuple; 633 634 Options options; 635 636 auto defaultGetoptArgs = tuple( 637 args, 638 "llvm-path", "The path to where the LLVM/Clang libraries are located.", &options.llvmLibPath, 639 // "ncurses-lib-path", "The path to the ncurses library.", &options.ncursesLibPath, 640 "statically-link-clang", "Statically link libclang. Defaults to no.", &options.staticallyLinkClang, 641 "statically-link-binary", "Completely statically link the binary. Defaults to no.", &options.staticallyLinkBinary 642 ); 643 644 version (OSX) 645 auto getoptArgs = defaultGetoptArgs; 646 else 647 { 648 auto getoptArgs = tuple( 649 defaultGetoptArgs.tupleof, 650 "statically-link-binary", "Completely statically link the binary. Defaults to no.", &options.staticallyLinkBinary 651 ); 652 } 653 654 auto help = getopt(defaultGetoptArgs.tupleof); 655 postProcessArguments(help, options); 656 657 return options; 658 } 659 660 /** 661 * Post processes the arguments. 662 * 663 * This will: 664 * $(UL 665 * $(LI Print the help/usage information, if that was requested) 666 * $(LI 667 * Set the `help` field of the `options` struct to `true`, if help was 668 * requested 669 * ) 670 * $(LI 671 * Set `staticallyLinkClang` to `true` if `staticallyLinkBinary` is 672 * true 673 * ) 674 * ) 675 * 676 * Params: 677 * result = the result value from the call to `getopt` 678 * options = the struct containing the parsed arguments 679 */ 680 void postProcessArguments(GetoptResult result, ref Options options) 681 { 682 if (options.staticallyLinkBinary) 683 options.staticallyLinkClang = true; 684 685 if (!result.helpWanted) 686 return; 687 688 options.help = true; 689 690 defaultGetoptPrinter("Usage: ./configure [options]\n\nOptions:", 691 result.options); 692 } 693 694 /** 695 * Enforces that a given set of libraries exist. 696 * 697 * Params: 698 * name = a textual representation of the set of libraries to check for. 699 * Will be used in error messages 700 * 701 * path = the path to the directory where to look for the libraries 702 * libraries = the actual libraries to look for 703 * 704 * Throws: Exception if any of the given libraries don't exist 705 */ 706 void enforceLibrariesExist(string name, string path, 707 const(string)[] libraries ...) 708 { 709 auto errorMessage = format("All required %s libraries could not be " ~ 710 "found in the path '%s'.\nRequired libraries are:\n%s", name, path, 711 libraries.join("\n")); 712 713 alias libraryExists = library => path.buildPath(library).exists; 714 715 enforce(libraries.all!(libraryExists), errorMessage); 716 } 717 718 /** 719 * Searches the `PATH` environment variable for the given filename. 720 * 721 * Params: 722 * filename = the filename to search for in the `PATH` 723 * 724 * Return: the full path to the given filename if found, otherwise `null` 725 */ 726 string searchPath(string filename) 727 { 728 auto path = 729 environment.get("PATH", ""). 730 split(':'). 731 map!(path => path.buildPath(filename)). 732 find!(exists); 733 734 return path.empty ? null : path.front; 735 }