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 "/data/data/com.termux/files/usr/lib", // Termux 205 ]; 206 207 enum debianPaths = [ 208 "/usr/lib/llvm-10/lib", 209 "/usr/lib/llvm-9/lib", 210 "/usr/lib/llvm-8/lib", 211 "/usr/lib/llvm-7/lib", 212 "/usr/lib/llvm-6.0/lib", 213 "/usr/lib/llvm-5.0/lib", 214 "/usr/lib/llvm-4.0/lib", 215 "/usr/lib/llvm-3.9/lib", 216 "/usr/lib/llvm-3.8/lib", 217 "/usr/lib/llvm-3.7/lib" 218 ]; 219 220 enum centOsPaths = [ 221 "/usr/lib64/llvm", 222 "/usr/lib32/llvm" 223 ]; 224 225 immutable llvmLibPaths = debianPaths ~ centOsPaths ~ standardPaths; 226 immutable additionalLibPaths = standardPaths; 227 228 enum additionalLib = LibraryName("tinfo", "libtinfo.a"); 229 enum cppLib = "stdc++"; 230 } 231 232 else version (FreeBSD) 233 { 234 enum clangLib = "libclang.so"; 235 236 enum standardPaths = [ 237 "/usr/lib", 238 "/usr/local/lib" 239 ]; 240 241 immutable llvmLibPaths = [ 242 "/usr/local/llvm90/lib", 243 "/usr/local/llvm80/lib", 244 "/usr/local/llvm70/lib", 245 "/usr/local/llvm60/lib", 246 "/usr/local/llvm50/lib", 247 ] ~ standardPaths; 248 249 immutable additionalLibPaths = standardPaths; 250 251 enum additionalLib = LibraryName("ncurses", "libncurses.a"); 252 enum cppLib = "c++"; 253 } 254 255 else 256 static assert(false, "Unsupported platform"); 257 258 /// The name of the LLVM configure binary. 259 enum llvmConfigExecutable = "llvm-config"; 260 } 261 262 /** 263 * This class represents a path to a file, like a library or an executable. 264 * 265 * It's the abstract base class for the `LibraryPath` and `LLVMConfigPath` 266 * subclasses. 267 */ 268 class Path 269 { 270 private 271 { 272 /** 273 * The name of the file this path represents. 274 * 275 * This is a name for the file that is used in error messages. 276 */ 277 string name; 278 279 /** 280 * A set of standard paths to which to search for the file this path 281 * represents. 282 */ 283 const(string)[] standardPaths; 284 285 /** 286 * The custom path that was specified when invoking this configuration 287 * script, or `null` if no custom path was specified. 288 */ 289 string specifiedPath; 290 291 /// The actual file to look for in `standardPaths` and `specifiedPath`. 292 string fileToCheck; 293 294 /// Local cache for the full path to the file. 295 string path_; 296 } 297 298 alias path this; 299 300 /** 301 * Constructs a new instance of this class. 302 * 303 * Params: 304 * name = the name of the file this path represents 305 * 306 * standardPaths = a set of standard paths to which to search for the file 307 * this path represents 308 * 309 * specifiedPath = the custom path that was specified when invoking this 310 * configuration script, or `null` if no custom path was specified 311 * 312 * fileToCheck = the actual file to look for in `standardPaths` and 313 * `specifiedPath` 314 */ 315 this(string name, const(string)[] standardPaths, 316 string specifiedPath, string fileToCheck) 317 { 318 this.name = name; 319 this.standardPaths = standardPaths; 320 this.specifiedPath = specifiedPath; 321 this.fileToCheck = fileToCheck; 322 } 323 324 /** 325 * Returns the full path to the file this path represents as a string. 326 * 327 * If `specifiedPath` is non-empty, `fileToCheck` will be searched for in 328 * `specifiedPath`. Otherwise `fileToCheck` will be searched for in 329 * `standardPaths`. 330 * 331 * Returns: the full path to the file this path represents 332 */ 333 string path() 334 { 335 if (path_.ptr) 336 return path_; 337 338 return path_ = specifiedPath.empty ? standardPath : customPath; 339 } 340 341 override string toString() 342 { 343 return path; 344 } 345 346 /** 347 * Returns the full path of `fileToCheck` by searching in `standardPaths`. 348 * 349 * Returns: the full path of `fileToCheck` by searching in `standardPaths` 350 * 351 * Throws: an `Exception` if `fileToCheck` cannot be found in any of the 352 * paths in `standardPath` 353 */ 354 string standardPath() 355 { 356 auto errorMessage = format("Could not find %s in any of the standard " ~ 357 "paths for %s: \n%s\nPlease specify a path manually using " ~ 358 "'./configure --%s-path=<path>'.", 359 fileToCheck, name, standardPaths.join('\n'), name.toLower 360 ); 361 362 auto result = standardPaths. 363 find!(exists). 364 find!(e => e.buildPath(fileToCheck).exists); 365 366 enforce(!result.empty, errorMessage); 367 368 return result.front.absolutePath; 369 } 370 371 private: 372 373 /** 374 * Returns the full path of `fileToCheck` by searching in `specifiedPath` 375 * and the `PATH` environment variable. 376 * 377 * If `fileToCheck` cannot be found in `specifiedPath` it will search for 378 * `fileToCheck` in the `PATH` environment variable. If that fails, an 379 * exception is thrown. 380 * 381 * Returns: the full path of `fileToCheck` 382 * 383 * Throws: an `Exception` if `fileToCheck` cannot be found in 384 * `specifiedPath` or the `PATH` environment variable 385 */ 386 string customPath() 387 { 388 auto path = specifiedPath.asAbsolutePath.asNormalizedPath.to!string; 389 390 auto errorMessage = format("The specified library %s in path '%s' " ~ 391 "does not exist.", name, path); 392 393 if (path.exists) 394 return path; 395 396 path = searchPath(specifiedPath); 397 enforce(path.exists, errorMessage); 398 399 return path; 400 } 401 } 402 403 /** 404 * This mixin template contains shared logic to generate the actual 405 * configuration. 406 */ 407 mixin template BaseConfigurator() 408 { 409 private 410 { 411 /// The name of the file where the configuration is written. 412 enum configPath = "linker_flags.txt"; 413 414 /// The options that were the result of parsing the command line flags. 415 Options options; 416 417 /// The default configuration. 418 DefaultConfig defaultConfig; 419 420 /// The LLVM/Clang library path. 421 Path llvmLibPath; 422 } 423 424 /** 425 * Initializes the receiver with the given arguments. This method acts as 426 * the shared constructor. 427 * 428 * Params: 429 * options = the options 430 * defaultConfig = the default configuration 431 */ 432 void initialize(Options options, DefaultConfig defaultConfig) 433 { 434 this.options = options; 435 this.defaultConfig = defaultConfig; 436 437 llvmLibPath = new Path( 438 "llvm", 439 defaultConfig.llvmLibPaths, 440 options.llvmLibPath, 441 defaultConfig.clangLib 442 ); 443 } 444 445 private: 446 447 /** 448 * Writes given configuration to the config file. 449 * 450 * Params: 451 * config = the configuration to write, that is, the linker flags 452 */ 453 void writeConfig(string config) 454 { 455 write(configPath, config); 456 } 457 458 /// Returns: the configuration, that is, the linker flags. 459 string config() 460 { 461 return flags.filter!(e => !e.empty).join("\n") ~ '\n'; 462 } 463 } 464 465 /** 466 * This struct contains the logic for generating the configuration for static 467 * linking. 468 */ 469 struct StaticConfigurator 470 { 471 mixin BaseConfigurator; 472 473 private 474 { 475 version (D_Ddoc) 476 { 477 /** 478 * Contains the `--start-group` flag on non-macOS platforms. 479 * 480 * Used on non-macOS platforms to group the LLVM and Clang 481 * libraries to be searched repeatedly to resolve undefined symbols. 482 */ 483 enum startGroupFlag = ""; 484 485 /** 486 * Contains the `--end-group` flag on non-macOS platforms. 487 * 488 * Used on non-macOS platforms to group the LLVM and Clang 489 * libraries to be searched repeatedly to resolve undefined symbols. 490 */ 491 enum endGroupFlag = ""; 492 } 493 494 else version (OSX) 495 { 496 enum startGroupFlag = "".only; 497 enum endGroupFlag = "".only; 498 } 499 500 else 501 { 502 enum startGroupFlag = "--start-group".only; 503 enum endGroupFlag = "-Wl,--end-group".only; 504 } 505 506 /// Local cache for the additional library path. 507 Path additionalLibPath; 508 } 509 510 /** 511 * Constructs a new instance of this struct with the given arguments. 512 * 513 * Params: 514 * options = the options 515 * defaultConfig = the default configuration 516 */ 517 this(Options options, DefaultConfig defaultConfig) 518 { 519 initialize(options, defaultConfig); 520 521 additionalLibPath = new Path(defaultConfig.additionalLib.name, 522 DefaultConfig.additionalLibPaths, 523 options.additionalLibPath, defaultConfig.additionalLib.filename); 524 } 525 526 /** 527 * Generates the actual configuration. 528 * 529 * This will locate all required libraries, build a set of linker flags and 530 * write the result to the configuration file. 531 */ 532 void generateConfig() 533 { 534 enforceLibrariesExist( 535 DefaultConfig.additionalLib.name, 536 additionalLibPath, 537 DefaultConfig.additionalLib.filename 538 ); 539 540 writeConfig(config); 541 } 542 543 private: 544 545 /// Return: a range of all the necessary linker flags. 546 auto flags() 547 { 548 return chain( 549 startGroupFlag, 550 libclangFlags, 551 llvmFlags, 552 endGroupFlag, 553 additionalLibFlags, 554 cppFlags, 555 extraFlags 556 ); 557 } 558 559 /** 560 * Returns: a range of linker flags necessary to link with the standard C++ 561 * library. 562 */ 563 auto cppFlags() 564 { 565 return format("-l%s", DefaultConfig.cppLib).only; 566 } 567 568 /** 569 * Returns: a range of linker flags necessary to link with the ncurses 570 * library. 571 */ 572 auto additionalLibFlags() 573 { 574 return additionalLibPath 575 .buildPath(DefaultConfig.additionalLib.filename) 576 .only; 577 } 578 579 /** 580 * Returns: a range of linker flags necessary to link with the LLVM 581 * libraries. 582 */ 583 auto llvmFlags() 584 { 585 const result = dirEntries(llvmLibPath, "libLLVM*.a", SpanMode.shallow) 586 .map!(e => e.name) 587 .array; 588 589 const findAllSymbolsPath = llvmLibPath.buildPath("libfindAllSymbols.a"); 590 591 return findAllSymbolsPath.exists ? result ~ findAllSymbolsPath : result; 592 } 593 594 /** 595 * Returns: a range of linker flags necessary to link with the Clang 596 * libraries. 597 */ 598 auto libclangFlags() 599 { 600 return dirEntries(llvmLibPath, "libclang*.a", SpanMode.shallow); 601 } 602 603 auto extraFlags() 604 { 605 return (options.staticallyLinkBinary ? "-static" : "").only; 606 } 607 } 608 609 /** 610 * This struct contains the logic for generating the configuration for dynamic 611 * linking. 612 */ 613 struct DynamicConfigurator 614 { 615 mixin BaseConfigurator; 616 617 /** 618 * Constructs a new instance of this struct with the given arguments. 619 * 620 * Params: 621 * options = the options 622 * defaultConfig = the default configuration 623 */ 624 this(Options options, DefaultConfig defaultConfig) 625 { 626 initialize(options, defaultConfig); 627 } 628 629 /** 630 * Generates the actual configuration. 631 * 632 * This will locate all required libraries, build a set of linker flags and 633 * write the result to the configuration file. 634 */ 635 void generateConfig() 636 { 637 enforceLibrariesExist("libclang", llvmLibPath, DefaultConfig.clangLib); 638 639 writeConfig(config); 640 } 641 642 private: 643 644 /// Return: a range of all the necessary linker flags. 645 auto flags() 646 { 647 return format("-L%1$s\n-lclang\n-Xlinker -rpath %1$s", llvmLibPath) 648 .only; 649 } 650 } 651 652 /// The main entry point of this script. 653 void main(string[] args) 654 { 655 auto options = parseArguments(args); 656 657 if (!options.help) 658 { 659 if (options.staticallyLinkClang) 660 StaticConfigurator(options, DefaultConfig()).generateConfig(); 661 else 662 DynamicConfigurator(options, DefaultConfig()).generateConfig(); 663 } 664 } 665 666 private: 667 668 /** 669 * Parses the command line arguments given to the application. 670 * 671 * Params: 672 * args = the command line arguments to parse 673 * 674 * Returns: the options set while parsing the arguments 675 */ 676 Options parseArguments(string[] args) 677 { 678 import std.typecons : tuple; 679 680 Options options; 681 682 auto defaultGetoptArgs = tuple( 683 args, 684 "llvm-path", "The path to the LLVM/Clang root directory.", &options.llvmPath, 685 // "ncurses-lib-path", "The path to the ncurses library.", &options.ncursesLibPath, 686 "statically-link-clang", "Statically link libclang. Defaults to no.", &options.staticallyLinkClang, 687 "statically-link-binary", "Completely statically link the binary. Defaults to no.", &options.staticallyLinkBinary 688 ); 689 690 version (OSX) 691 auto getoptArgs = defaultGetoptArgs; 692 else 693 { 694 auto getoptArgs = tuple( 695 defaultGetoptArgs.tupleof, 696 "statically-link-binary", "Completely statically link the binary. Defaults to no.", &options.staticallyLinkBinary 697 ); 698 } 699 700 auto help = getopt(defaultGetoptArgs.tupleof); 701 postProcessArguments(help, options); 702 703 return options; 704 } 705 706 /** 707 * Post processes the arguments. 708 * 709 * This will: 710 * $(UL 711 * $(LI Print the help/usage information, if that was requested) 712 * $(LI 713 * Set the `help` field of the `options` struct to `true`, if help was 714 * requested 715 * ) 716 * $(LI 717 * Set `staticallyLinkClang` to `true` if `staticallyLinkBinary` is 718 * true 719 * ) 720 * ) 721 * 722 * Params: 723 * result = the result value from the call to `getopt` 724 * options = the struct containing the parsed arguments 725 */ 726 void postProcessArguments(GetoptResult result, ref Options options) 727 { 728 if (options.staticallyLinkBinary) 729 options.staticallyLinkClang = true; 730 731 if (!result.helpWanted) 732 return; 733 734 options.help = true; 735 736 defaultGetoptPrinter("Usage: ./configure [options]\n\nOptions:", 737 result.options); 738 } 739 740 /** 741 * Enforces that a given set of libraries exist. 742 * 743 * Params: 744 * name = a textual representation of the set of libraries to check for. 745 * Will be used in error messages 746 * 747 * path = the path to the directory where to look for the libraries 748 * libraries = the actual libraries to look for 749 * 750 * Throws: Exception if any of the given libraries don't exist 751 */ 752 void enforceLibrariesExist(string name, string path, 753 const(string)[] libraries ...) 754 { 755 auto errorMessage = format("All required %s libraries could not be " ~ 756 "found in the path '%s'.\nRequired libraries are:\n%s", name, path, 757 libraries.join("\n")); 758 759 alias libraryExists = library => path.buildPath(library).exists; 760 761 enforce(libraries.all!(libraryExists), errorMessage); 762 } 763 764 /** 765 * Searches the `PATH` environment variable for the given filename. 766 * 767 * Params: 768 * filename = the filename to search for in the `PATH` 769 * 770 * Return: the full path to the given filename if found, otherwise `null` 771 */ 772 string searchPath(string filename) 773 { 774 auto path = 775 environment.get("PATH", ""). 776 split(':'). 777 map!(path => path.buildPath(filename)). 778 find!(exists); 779 780 return path.empty ? null : path.front; 781 }