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