1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2014-2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import Basics
import Dispatch
import OrderedCollections
import PackageModel
import func TSCBasic.findCycle
import struct TSCBasic.KeyedPair
import func TSCBasic.topologicalSort
/// An error in the structure or layout of a package.
public enum ModuleError: Swift.Error {
/// Describes a way in which a package layout is invalid.
public enum InvalidLayoutType {
case multipleSourceRoots([AbsolutePath])
case modulemapInSources(AbsolutePath)
case modulemapMissing(AbsolutePath)
}
/// Indicates two targets with the same name and their corresponding packages.
case duplicateModule(moduleName: String, packages: [PackageIdentity])
/// The referenced target could not be found.
case moduleNotFound(String, TargetDescription.TargetKind, shouldSuggestRelaxedSourceDir: Bool)
/// The artifact for the binary target could not be found.
case artifactNotFound(moduleName: String, expectedArtifactName: String)
/// Invalid module alias.
case invalidModuleAlias(originalName: String, newName: String)
/// Invalid custom path.
case invalidCustomPath(moduleName: String, path: String)
/// Package layout is invalid.
case invalidLayout(InvalidLayoutType)
/// The manifest has invalid configuration wrt type of the target.
case invalidManifestConfig(String, String)
/// The target dependency declaration has cycle in it.
case cycleDetected((path: [String], cycle: [String]))
/// The public headers directory is at an invalid path.
case invalidPublicHeadersDirectory(String)
/// The sources of a target are overlapping with another target.
case overlappingSources(target: String, sources: [AbsolutePath])
/// We found multiple test entry point files.
case multipleTestEntryPointFilesFound(package: String, files: [AbsolutePath])
/// The tools version in use is not compatible with target's sources.
case incompatibleToolsVersions(package: String, required: [SwiftLanguageVersion], current: ToolsVersion)
/// The target path is outside the package.
case targetOutsidePackage(package: String, target: String)
/// Unsupported target path
case unsupportedTargetPath(String)
/// Invalid header search path.
case invalidHeaderSearchPath(String)
/// Default localization not set in the presence of localized resources.
case defaultLocalizationNotSet
/// A plugin target didn't declare a capability.
case pluginCapabilityNotDeclared(target: String)
/// A C target has declared an embedded resource
case embedInCodeNotSupported(target: String)
/// Indicates several targets with the same name exist in packages
case duplicateModules(package: PackageIdentity, otherPackage: PackageIdentity, modules: [String])
/// Indicates several targets with the same name exist in a registry and scm package
case duplicateModulesScmAndRegistry(
regsitryPackage: PackageIdentity.RegistryIdentity,
scmPackage: PackageIdentity,
modules: [String]
)
}
extension ModuleError: CustomStringConvertible {
public var description: String {
switch self {
case .duplicateModule(let target, let packages):
let packages = packages.map(\.description).sorted().joined(separator: "', '")
return "multiple packages ('\(packages)') declare targets with a conflicting name: '\(target)ā; target names need to be unique across the package graph"
case .moduleNotFound(let target, let type, let shouldSuggestRelaxedSourceDir):
let folderName = (type == .test) ? "Tests" : (type == .plugin) ? "Plugins" : "Sources"
var clauses = ["Source files for target \(target) should be located under '\(folderName)/\(target)'"]
if shouldSuggestRelaxedSourceDir {
clauses.append("'\(folderName)'")
}
clauses.append("or a custom sources path can be set with the 'path' property in Package.swift")
return clauses.joined(separator: ", ")
case .artifactNotFound(let targetName, let expectedArtifactName):
return "binary target '\(targetName)' could not be mapped to an artifact with expected name '\(expectedArtifactName)'"
case .invalidModuleAlias(let originalName, let newName):
return "empty or invalid module alias; ['\(originalName)': '\(newName)']"
case .invalidLayout(let type):
return "package has unsupported layout; \(type)"
case .invalidManifestConfig(let package, let message):
return "configuration of package '\(package)' is invalid; \(message)"
case .cycleDetected(let cycle):
return "cyclic dependency declaration found: " +
(cycle.path + cycle.cycle).joined(separator: " -> ") +
" -> " + cycle.cycle[0]
case .invalidPublicHeadersDirectory(let name):
return "public headers (\"include\") directory path for '\(name)' is invalid or not contained in the target"
case .overlappingSources(let target, let sources):
return "target '\(target)' has overlapping sources: " +
sources.map(\.description).joined(separator: ", ")
case .multipleTestEntryPointFilesFound(let package, let files):
return "package '\(package)' has multiple test entry point files: " +
files.map(\.description).sorted().joined(separator: ", ")
case .incompatibleToolsVersions(let package, let required, let current):
if required.isEmpty {
return "package '\(package)' supported Swift language versions is empty"
}
return "package '\(package)' requires minimum Swift language version \(required[0]) which is not supported by the current tools version (\(current))"
case .targetOutsidePackage(let package, let target):
return "target '\(target)' in package '\(package)' is outside the package root"
case .unsupportedTargetPath(let targetPath):
return "target path '\(targetPath)' is not supported; it should be relative to package root"
case .invalidCustomPath(let target, let path):
return "invalid custom path '\(path)' for target '\(target)'"
case .invalidHeaderSearchPath(let path):
return "invalid header search path '\(path)'; header search path should not be outside the package root"
case .defaultLocalizationNotSet:
return "manifest property 'defaultLocalization' not set; it is required in the presence of localized resources"
case .pluginCapabilityNotDeclared(let target):
return "plugin target '\(target)' doesn't have a 'capability' property"
case .embedInCodeNotSupported(let target):
return "embedding resources in code not supported for C-family language target \(target)"
case .duplicateModules(let package, let otherPackage, let targets):
var targetsDescription = "'\(targets.sorted().prefix(3).joined(separator: "', '"))'"
if targets.count > 3 {
targetsDescription += " and \(targets.count - 3) others"
}
return """
multiple similar targets \(targetsDescription) appear in package '\(package)' and '\(otherPackage)', \
this may indicate that the two packages are the same and can be de-duplicated by using mirrors. \
if they are not duplicate consider using the `moduleAliases` parameter in manifest to provide unique names
"""
case .duplicateModulesScmAndRegistry(let registryPackage, let scmPackage, let targets):
var targetsDescription = "'\(targets.sorted().prefix(3).joined(separator: "', '"))'"
if targets.count > 3 {
targetsDescription += " and \(targets.count - 3) others"
}
return """
multiple similar targets \(targetsDescription) appear in registry package '\(
registryPackage
)' and source control package '\(scmPackage)', \
this may indicate that the two packages are the same and can be de-duplicated \
by activating the automatic source-control to registry replacement, or by using mirrors. \
if they are not duplicate consider using the `moduleAliases` parameter in manifest to provide unique names
"""
}
}
}
extension ModuleError.InvalidLayoutType: CustomStringConvertible {
public var description: String {
switch self {
case .multipleSourceRoots(let paths):
"multiple source roots found: " + paths.map(\.description).sorted().joined(separator: ", ")
case .modulemapInSources(let path):
"modulemap '\(path)' should be inside the 'include' directory"
case .modulemapMissing(let path):
"missing system target module map at '\(path)'"
}
}
}
extension Module {
/// An error in the organization or configuration of an individual target.
enum Error: Swift.Error {
/// The target's name is invalid.
case invalidName(path: RelativePath, problem: ModuleNameProblem)
enum ModuleNameProblem {
/// Empty target name.
case emptyName
}
/// The target contains an invalid mix of languages (e.g. both Swift and C).
case mixedSources(AbsolutePath)
}
}
extension Module.Error: CustomStringConvertible {
var description: String {
switch self {
case .invalidName(let path, let problem):
"invalid target name at '\(path)'; \(problem)"
case .mixedSources(let path):
"target at '\(path)' contains mixed language source files; feature not supported"
}
}
}
extension Module.Error.ModuleNameProblem: CustomStringConvertible {
var description: String {
switch self {
case .emptyName:
"target names can not be empty"
}
}
}
extension Product {
/// An error in a product definition.
enum Error: Swift.Error {
case emptyName
case moduleEmpty(product: String, target: String)
}
}
extension Product.Error: CustomStringConvertible {
var description: String {
switch self {
case .emptyName:
"product names can not be empty"
case .moduleEmpty(let product, let target):
"target '\(target)' referenced in product '\(product)' is empty"
}
}
}
/// A structure representing the remote artifact information necessary to construct the package.
public struct BinaryArtifact {
/// The kind of the artifact.
public let kind: BinaryModule.Kind
/// The URL the artifact was downloaded from.
public let originURL: String?
/// The path to the artifact.
public let path: AbsolutePath
public init(kind: BinaryModule.Kind, originURL: String?, path: AbsolutePath) {
self.kind = kind
self.originURL = originURL
self.path = path
}
}
/// Helper for constructing a package following the convention system.
///
/// The 'builder' here refers to the builder pattern and not any build system
/// related function.
public final class PackageBuilder {
/// Predefined source directories, in order of preference.
public static let predefinedSourceDirectories = ["Sources", "Source", "src", "srcs"]
/// Predefined test directories, in order of preference.
public static let predefinedTestDirectories = ["Tests", "Sources", "Source", "src", "srcs"]
/// Predefined plugin directories, in order of preference.
public static let predefinedPluginDirectories = ["Plugins"]
/// The identity for the package being constructed.
private let identity: PackageIdentity
/// The manifest for the package being constructed.
private let manifest: Manifest
/// The product filter to apply to the package.
private let productFilter: ProductFilter
/// The path of the package.
private let packagePath: AbsolutePath
/// Information concerning the different downloaded or local (archived) binary target artifacts.
private let binaryArtifacts: [String: BinaryArtifact]
/// Create multiple test products.
///
/// If set to true, one test product will be created for each test target.
private let shouldCreateMultipleTestProducts: Bool
/// Path to test entry point file, if specified explicitly.
private let testEntryPointPath: AbsolutePath?
/// Temporary parameter controlling whether to warn about implicit executable targets when tools version is 5.4.
private let warnAboutImplicitExecutableTargets: Bool
/// Create the special REPL product for this package.
private let createREPLProduct: Bool
/// The additional file detection rules.
private let additionalFileRules: [FileRuleDescription]
/// ObservabilityScope with which to emit diagnostics
private let observabilityScope: ObservabilityScope
/// The filesystem package builder will run on.
private let fileSystem: FileSystem
private var platformRegistry: PlatformRegistry {
PlatformRegistry.default
}
// The set of the sources computed so far, used to validate source overlap
private var allSources = Set<AbsolutePath>()
// Caches all declared versions for this package.
private var declaredSwiftVersionsCache: [SwiftLanguageVersion]? = nil
// Caches the version we chose to build for.
private var swiftVersionCache: SwiftLanguageVersion? = nil
/// Create a builder for the given manifest and package `path`.
///
/// - Parameters:
/// - identity: The identity of this package.
/// - manifest: The manifest of this package.
/// - path: The root path of the package.
/// - artifactPaths: Paths to the downloaded binary target artifacts.
/// - createMultipleTestProducts: If enabled, create one test product for
/// each test target.
/// - fileSystem: The file system on which the builder should be run.///
public init(
identity: PackageIdentity,
manifest: Manifest,
productFilter: ProductFilter,
path: AbsolutePath,
additionalFileRules: [FileRuleDescription],
binaryArtifacts: [String: BinaryArtifact],
shouldCreateMultipleTestProducts: Bool = false,
testEntryPointPath: AbsolutePath? = nil,
warnAboutImplicitExecutableTargets: Bool = true,
createREPLProduct: Bool = false,
fileSystem: FileSystem,
observabilityScope: ObservabilityScope
) {
self.identity = identity
self.manifest = manifest
self.productFilter = productFilter
self.packagePath = path
self.additionalFileRules = additionalFileRules
self.binaryArtifacts = binaryArtifacts
self.shouldCreateMultipleTestProducts = shouldCreateMultipleTestProducts
self.testEntryPointPath = testEntryPointPath
self.createREPLProduct = createREPLProduct
self.warnAboutImplicitExecutableTargets = warnAboutImplicitExecutableTargets
self.observabilityScope = observabilityScope.makeChildScope(
description: "PackageBuilder",
metadata: .packageMetadata(identity: self.identity, kind: self.manifest.packageKind)
)
self.fileSystem = fileSystem
}
/// Build a new package following the conventions.
public func construct() throws -> Package {
let targets = try self.constructTargets()
let products = try self.constructProducts(targets)
// Find the special directory for targets.
let targetSpecialDirs = self.findTargetSpecialDirs(targets)
return Package(
identity: self.identity,
manifest: self.manifest,
path: self.packagePath,
targets: targets,
products: products,
targetSearchPath: self.packagePath.appending(component: targetSpecialDirs.targetDir),
testTargetSearchPath: self.packagePath.appending(component: targetSpecialDirs.testTargetDir)
)
}
/// Computes the special directory where targets are present or should be placed in future.
private func findTargetSpecialDirs(_ targets: [Module]) -> (targetDir: String, testTargetDir: String) {
let predefinedDirs = self.findPredefinedTargetDirectory()
// Select the preferred tests directory.
var testTargetDir = PackageBuilder.predefinedTestDirectories[0]
// If found predefined test directory is not same as preferred test directory,
// check if any of the test target is actually inside the predefined test directory.
if predefinedDirs.testTargetDir != testTargetDir {
let expectedTestsDir = self.packagePath.appending(component: predefinedDirs.testTargetDir)
for target in targets where target.type == .test {
// If yes, use the predefined test directory as preferred test directory.
if expectedTestsDir == target.sources.root.parentDirectory {
testTargetDir = predefinedDirs.testTargetDir
break
}
}
}
return (predefinedDirs.targetDir, testTargetDir)
}
// MARK: Utility Predicates
private func isValidSource(_ path: AbsolutePath) -> Bool {
// Ignore files which don't match the expected extensions.
guard let ext = path.extension,
SupportedLanguageExtension.validExtensions(toolsVersion: self.manifest.toolsVersion).contains(ext)
else {
return false
}
let basename = path.basename
// Ignore dotfiles.
if basename.hasPrefix(".") { return false }
// Ignore test entry point files.
if SwiftModule.testEntryPointNames.contains(basename) { return false }
// Ignore paths which are not valid files.
if !self.fileSystem.isFile(path) {
// Diagnose broken symlinks.
if self.fileSystem.isSymlink(path) {
self.observabilityScope.emit(.brokenSymlink(path))
}
return false
}
// Ignore manifest files.
if path.parentDirectory == self.packagePath {
if basename == Manifest.filename { return false }
// Ignore version-specific manifest files.
if basename.hasPrefix(Manifest.basename + "@") && basename.hasSuffix(".swift") {
return false
}
}
// Otherwise, we have a valid source file.
return true
}
/// Returns path to all the items in a directory.
// FIXME: This is generic functionality, and should move to FileSystem.
func directoryContents(_ path: AbsolutePath) throws -> [AbsolutePath] {
try self.fileSystem.getDirectoryContents(path).map { path.appending(component: $0) }
}
/// Private function that creates and returns a list of targets defined by a package.
private func constructTargets() throws -> [Module] {
// Check for a modulemap file, which indicates a system target.
let moduleMapPath = self.packagePath.appending(component: moduleMapFilename)
if self.fileSystem.isFile(moduleMapPath) {
// Warn about any declared targets.
if !self.manifest.targets.isEmpty {
self.observabilityScope.emit(
.systemPackageDeclaresTargets(targets: Array(self.manifest.targets.map(\.name)))
)
}
// Emit deprecation notice.
if self.manifest.toolsVersion >= .v4_2 {
self.observabilityScope.emit(.systemPackageDeprecation)
}
// Package contains a modulemap at the top level, so we assuming
// it's a system library target.
return [
SystemLibraryModule(
name: self.manifest.displayName, // FIXME: use identity instead?
path: self.packagePath,
isImplicit: true,
pkgConfig: self.manifest.pkgConfig,
providers: self.manifest.providers
),
]
}
// At this point the target can't be a system target, make sure manifest doesn't contain
// system target specific configuration.
guard self.manifest.pkgConfig == nil else {
throw ModuleError.invalidManifestConfig(
self.identity.description, "the 'pkgConfig' property can only be used with a System Module Package"
)
}
guard self.manifest.providers == nil else {
throw ModuleError.invalidManifestConfig(
self.identity.description, "the 'providers' property can only be used with a System Module Package"
)
}
return try self.constructV4Targets()
}
/// Finds the predefined directories for regular targets, test targets, and plugin targets.
private func findPredefinedTargetDirectory()
-> (targetDir: String, testTargetDir: String, pluginTargetDir: String)
{
let targetDir = PackageBuilder.predefinedSourceDirectories.first(where: {
self.fileSystem.isDirectory(self.packagePath.appending(component: $0))
}) ?? PackageBuilder.predefinedSourceDirectories[0]
let testTargetDir = PackageBuilder.predefinedTestDirectories.first(where: {
self.fileSystem.isDirectory(self.packagePath.appending(component: $0))
}) ?? PackageBuilder.predefinedTestDirectories[0]
let pluginTargetDir = PackageBuilder.predefinedPluginDirectories.first(where: {
self.fileSystem.isDirectory(self.packagePath.appending(component: $0))
}) ?? PackageBuilder.predefinedPluginDirectories[0]
return (targetDir, testTargetDir, pluginTargetDir)
}
/// Construct targets according to PackageDescription 4 conventions.
private func constructV4Targets() throws -> [Module] {
// Select the correct predefined directory list.
let predefinedDirs = self.findPredefinedTargetDirectory()
let predefinedTargetDirectory = PredefinedTargetDirectory(
fs: fileSystem,
path: packagePath.appending(component: predefinedDirs.targetDir)
)
let predefinedTestTargetDirectory = PredefinedTargetDirectory(
fs: fileSystem,
path: packagePath.appending(component: predefinedDirs.testTargetDir)
)
let predefinedPluginTargetDirectory = PredefinedTargetDirectory(
fs: fileSystem,
path: packagePath.appending(component: predefinedDirs.pluginTargetDir)
)
/// Returns the path of the given target.
func findPath(for target: TargetDescription) throws -> AbsolutePath {
if target.type == .binary {
guard let artifact = self.binaryArtifacts[target.name] else {
throw ModuleError.artifactNotFound(moduleName: target.name, expectedArtifactName: target.name)
}
return artifact.path
} else if let subpath = target.path { // If there is a custom path defined, use that.
if subpath == "" || subpath == "." {
return self.packagePath
}
// Make sure target is not referenced by absolute path
guard let relativeSubPath = try? RelativePath(validating: subpath) else {
throw ModuleError.unsupportedTargetPath(subpath)
}
let path = self.packagePath.appending(relativeSubPath)
// Make sure the target is inside the package root.
guard path.isDescendantOfOrEqual(to: self.packagePath) else {
throw ModuleError.targetOutsidePackage(package: self.identity.description, target: target.name)
}
if self.fileSystem.isDirectory(path) {
return path
}
throw ModuleError.invalidCustomPath(moduleName: target.name, path: subpath)
}
// Check if target is present in the predefined directory.
let predefinedDir: PredefinedTargetDirectory = switch target.type {
case .test:
predefinedTestTargetDirectory
case .plugin:
predefinedPluginTargetDirectory
default:
predefinedTargetDirectory
}
let path = predefinedDir.path.appending(component: target.name)
// Return the path if the predefined directory contains it.
if predefinedDir.contents.contains(target.name) {
return path
}
let commonTargetsOfSimilarType = self.manifest.targetsWithCommonSourceRoot(type: target.type).count
// If there is only one target defined, it may be allowed to occupy the
// entire predefined target directory.
if self.manifest.toolsVersion >= .v5_9 {
if commonTargetsOfSimilarType == 1 {
return predefinedDir.path
}
}
// Otherwise, if the path "exists" then the case in manifest differs from the case on the file system.
if self.fileSystem.isDirectory(path) {
self.observabilityScope.emit(.targetNameHasIncorrectCase(target: target.name))
return path
}
throw ModuleError.moduleNotFound(
target.name,
target.type,
shouldSuggestRelaxedSourceDir: self.manifest
.shouldSuggestRelaxedSourceDir(type: target.type)
)
}
// Create potential targets.
let potentialTargets: [PotentialModule]
potentialTargets = try self.manifest.targetsRequired(for: self.productFilter).map { target in
let path = try findPath(for: target)
return PotentialModule(
name: target.name,
path: path,
type: target.type,
packageAccess: target.packageAccess
)
}
let targets = try createModules(potentialTargets)
let snippetTargets: [Module]
if self.manifest.packageKind.isRoot {
// Snippets: depend on all available library targets in the package.
// TODO: Do we need to filter out targets that aren't available on the host platform?
let productTargets = Set(manifest.products.flatMap(\.targets))
let snippetDependencies = targets
.filter { $0.type == .library && productTargets.contains($0.name) }
.map { Module.Dependency.module($0, conditions: []) }
snippetTargets = try createSnippetModules(dependencies: snippetDependencies)
} else {
snippetTargets = []
}
return targets + snippetTargets
}
// Create targets from the provided potential targets.
private func createModules(_ potentialModules: [PotentialModule]) throws -> [Module] {
// Find if manifest references a target which isn't present on disk.
let allVisibleModuleNames = self.manifest.visibleModuleNames(for: self.productFilter)
let potentialModulesName = Set(potentialModules.map(\.name))
let missingModuleNames = allVisibleModuleNames.subtracting(potentialModulesName)
if let missingModuleName = missingModuleNames.first {
let type = potentialModules.first(where: { $0.name == missingModuleName })?.type ?? .regular
throw ModuleError.moduleNotFound(
missingModuleName,
type,
shouldSuggestRelaxedSourceDir: self.manifest.shouldSuggestRelaxedSourceDir(type: type)
)
}
let products = Dictionary(manifest.products.map { ($0.name, $0) }, uniquingKeysWith: { $1 })
// If there happens to be a plugin product with the right name in the same package, we want to use that
// automatically.
func pluginTargetName(for productName: String) -> String? {
if let product = products[productName], product.type == .plugin {
product.targets.first
} else {
nil
}
}
let potentialModuleMap = Dictionary(potentialModules.map { ($0.name, $0) }, uniquingKeysWith: { $1 })
let successors: (PotentialModule) -> [PotentialModule] = {
// No reference of this target in manifest, i.e. it has no dependencies.
guard let target = self.manifest.targetMap[$0.name] else { return [] }
// Collect the successors from declared dependencies.
var successors: [PotentialModule] = target.dependencies.compactMap {
switch $0 {
case .target(let name, _):
// Since we already checked above that all referenced targets
// has to present, we always expect this target to be present in
// potentialModules dictionary.
potentialModuleMap[name]!
case .product:
nil
case .byName(let name, _):
// By name dependency may or may not be a target dependency.
potentialModuleMap[name]
}
}
// If there are plugin usages, consider them to be dependencies too.
if let pluginUsages = target.pluginUsages {
successors += pluginUsages.compactMap {
switch $0 {
case .plugin(_, .some(_)):
nil
case .plugin(let name, nil):
if let potentialModule = potentialModuleMap[name] {
potentialModule
} else if let targetName = pluginTargetName(for: name),
let potentialModule = potentialModuleMap[targetName]
{
potentialModule
} else {
nil
}
}
}
}
return successors
}
// Look for any cycle in the dependencies.
if let cycle = findCycle(potentialModules.sorted(by: { $0.name < $1.name }), successors: successors) {
throw ModuleError.cycleDetected((cycle.path.map(\.name), cycle.cycle.map(\.name)))
}
// There was no cycle so we sort the targets topologically.
let potentialModules = try topologicalSort(potentialModules, successors: successors)
// The created targets mapped to their name.
var targets = [String: Module]()
// If a directory is empty, we don't create a target object for them.
var emptyModules = Set<String>()
// Start iterating the potential targets.
for potentialModule in potentialModules.lazy.reversed() {
// Validate the target name. This function will throw an error if it detects a problem.
try validateModuleName(potentialModule.path, potentialModule.name, isTest: potentialModule.isTest)
// Get the target from the manifest.
let manifestTarget = manifest.targetMap[potentialModule.name]
// Get the dependencies of this target.
let dependencies: [Module.Dependency] = try manifestTarget.map {
try $0.dependencies.compactMap { dependency -> Module.Dependency? in
switch dependency {
case .target(let name, let condition):
// We don't create an object for targets which have no sources.
if emptyModules.contains(name) { return nil }
guard let target = targets[name] else { return nil }
return .module(target, conditions: buildConditions(from: condition))
case .product(let name, let package, let moduleAliases, let condition):
try validateModuleAliases(moduleAliases)
return .product(
.init(name: name, package: package, moduleAliases: moduleAliases),
conditions: buildConditions(from: condition)
)
case .byName(let name, let condition):
// We don't create an object for targets which have no sources.
if emptyModules.contains(name) { return nil }
if let target = targets[name] {
return .module(target, conditions: buildConditions(from: condition))
} else if potentialModuleMap[name] == nil {
return .product(
.init(name: name, package: nil),
conditions: buildConditions(from: condition)
)
} else {
return nil
}
}
}
} ?? []
// Get dependencies from the plugin usages of this target.
let pluginUsages: [Module.PluginUsage] = manifestTarget?.pluginUsages.map {
$0.compactMap { usage in
switch usage {
case .plugin(let name, let package):
if let package {
return .product(Module.ProductReference(name: name, package: package), conditions: [])
} else {
if let target = targets[name] {
return .module(target, conditions: [])
} else if let targetName = pluginTargetName(for: name), let target = targets[targetName] {
return .module(target, conditions: [])
} else {
self.observabilityScope.emit(.pluginNotFound(name: name))
return nil
}
}
}
}
} ?? []
// Create the target, adding the inferred dependencies from plugin usages to the declared dependencies.
let target = try createTarget(
potentialModule: potentialModule,
manifestTarget: manifestTarget,
dependencies: dependencies + pluginUsages
)
// Add the created target to the map or print no sources warning.
if let createdTarget = target {
targets[createdTarget.name] = createdTarget
} else {
emptyModules.insert(potentialModule.name)
self.observabilityScope.emit(.targetHasNoSources(
name: potentialModule.name,
type: potentialModule.type,
shouldSuggestRelaxedSourceDir: manifest
.shouldSuggestRelaxedSourceDir(
type: potentialModule
.type
)
))
}
}
return targets.values.sorted { $0.name > $1.name }
}
/// Private function that checks whether a target name is valid. This method doesn't return anything, but rather,
/// if there's a problem, it throws an error describing what the problem is.
private func validateModuleName(_ path: AbsolutePath, _ name: String, isTest: Bool) throws {
if name.isEmpty {
throw Module.Error.invalidName(
path: path.relative(to: self.packagePath),
problem: .emptyName
)
}
}
/// Validates module alias key and value pairs and throws an error if empty or contains invalid characters.
private func validateModuleAliases(_ aliases: [String: String]?) throws {
guard let aliases else { return }
for (aliasKey, aliasValue) in aliases {
if !aliasKey.isValidIdentifier ||
!aliasValue.isValidIdentifier ||
aliasKey == aliasValue
{
throw ModuleError.invalidModuleAlias(originalName: aliasKey, newName: aliasValue)
}
}
}
/// Private function that constructs a single Target object for the potential target.
private func createTarget(
potentialModule: PotentialModule,
manifestTarget: TargetDescription?,
dependencies: [Module.Dependency]
) throws -> Module? {
guard let manifestTarget else { return nil }
// Create system library target.
if potentialModule.type == .system {
let moduleMapPath = potentialModule.path.appending(component: moduleMapFilename)
guard self.fileSystem.isFile(moduleMapPath) else {
throw ModuleError.invalidLayout(.modulemapMissing(moduleMapPath))
}
return SystemLibraryModule(
name: potentialModule.name,
path: potentialModule.path, isImplicit: false,
pkgConfig: manifestTarget.pkgConfig,
providers: manifestTarget.providers
)
} else if potentialModule.type == .binary {
guard let artifact = self.binaryArtifacts[potentialModule.name] else {
throw InternalError("unknown binary artifact for '\(potentialModule.name)'")
}
let artifactOrigin: BinaryModule.Origin = artifact.originURL.flatMap { .remote(url: $0) } ?? .local
return BinaryModule(
name: potentialModule.name,
kind: artifact.kind,
path: potentialModule.path,
origin: artifactOrigin
)
}
// Check for duplicate target dependencies
if self.manifest.disambiguateByProductIDs {
let dupProductIDs = dependencies.compactMap { $0.product?.identity }.spm_findDuplicates()
for dupProductID in dupProductIDs {
let comps = dupProductID.components(separatedBy: "_")
let pkg = comps.first ?? ""
let name = comps.dropFirst().joined(separator: "_")
let dupProductName = name.isEmpty ? dupProductID : name
self.observabilityScope.emit(.duplicateProduct(name: dupProductName, package: pkg))
}
let dupTargetNames = dependencies.compactMap { $0.module?.name }.spm_findDuplicates()
for dupTargetName in dupTargetNames {
self.observabilityScope.emit(.duplicateTargetDependency(
dependency: dupTargetName,
target: potentialModule.name,
package: self.identity.description
))
}
} else {
dependencies.filter { $0.product?.moduleAliases == nil }.spm_findDuplicateElements(by: \.nameAndType)
.map(\.[0].name).forEach {
self.observabilityScope
.emit(.duplicateTargetDependency(
dependency: $0,
target: potentialModule.name,
package: self.identity.description
))
}
}
// Create the build setting assignment table for this target.
let buildSettings = try self.buildSettings(
for: manifestTarget,
targetRoot: potentialModule.path,
cxxLanguageStandard: self.manifest.cxxLanguageStandard,
toolsSwiftVersion: self.toolsSwiftVersion()
)
// Compute the path to public headers directory.
let publicHeaderComponent = manifestTarget.publicHeadersPath ?? ClangModule.defaultPublicHeadersComponent
let publicHeadersPath = try potentialModule.path.appending(RelativePath(validating: publicHeaderComponent))
guard publicHeadersPath.isDescendantOfOrEqual(to: potentialModule.path) else {
throw ModuleError.invalidPublicHeadersDirectory(potentialModule.name)
}
let sourcesBuilder = TargetSourcesBuilder(
packageIdentity: self.identity,
packageKind: self.manifest.packageKind,
packagePath: self.packagePath,
target: manifestTarget,
path: potentialModule.path,
defaultLocalization: self.manifest.defaultLocalization,
additionalFileRules: self.additionalFileRules,
toolsVersion: self.manifest.toolsVersion,
fileSystem: self.fileSystem,
observabilityScope: self.observabilityScope
)
let (sources, resources, headers, ignored, others) = try sourcesBuilder.run()
// Make sure defaultLocalization is set if the target has localized resources.
let hasLocalizedResources = resources.contains(where: { $0.localization != nil })
if hasLocalizedResources && self.manifest.defaultLocalization == nil {
throw ModuleError.defaultLocalizationNotSet
}
// FIXME: use identity instead?
// The name of the bundle, if one is being generated.
let potentialBundleName = self.manifest.displayName + "_" + potentialModule.name
if sources.relativePaths.isEmpty && resources.isEmpty && headers.isEmpty {
return nil
}
try self.validateSourcesOverlapping(forTarget: potentialModule.name, sources: sources.paths)
// Deal with package plugin targets.
if potentialModule.type == .plugin {
// Check that the target has a declared capability; we should not have come this far if not.
guard let declaredCapability = manifestTarget.pluginCapability else {
throw ModuleError.pluginCapabilityNotDeclared(target: manifestTarget.name)
}
// Create and return an PluginTarget configured with the information from the manifest.
return PluginModule(
name: potentialModule.name,
sources: sources,
apiVersion: self.manifest.toolsVersion,
pluginCapability: PluginCapability(from: declaredCapability),
dependencies: dependencies,
packageAccess: potentialModule.packageAccess
)
}
/// Determine the module's kind, or leave nil to check the source directory.
let moduleKind: Module.Kind
switch potentialModule.type {
case .test:
moduleKind = .test
case .executable:
moduleKind = .executable
case .macro:
moduleKind = .macro
default:
moduleKind = sources.computeModuleKind()
if moduleKind == .executable && self.manifest.toolsVersion >= .v5_4 && self
.warnAboutImplicitExecutableTargets
{
self.observabilityScope
.emit(
warning: "'\(potentialModule.name)' was identified as an executable target given the presence of a 'main.swift' file. Starting with tools version \(ToolsVersion.v5_4) executable targets should be declared as 'executableTarget()'"
)
}
}
// Create and return the right kind of target depending on what kind of sources we found.
if sources.hasSwiftSources {
return try SwiftModule(
name: potentialModule.name,
potentialBundleName: potentialBundleName,
type: moduleKind,
path: potentialModule.path,
sources: sources,
resources: resources,
ignored: ignored,
others: others,
dependencies: dependencies,
packageAccess: potentialModule.packageAccess,
declaredSwiftVersions: self.declaredSwiftVersions(),
buildSettings: buildSettings,
buildSettingsDescription: manifestTarget.settings,
usesUnsafeFlags: manifestTarget.usesUnsafeFlags
)
} else {
// It's not a Swift target, so it's a Clang target (those are the only two types of source target currently
// supported).
// First determine the type of module map that will be appropriate for the target based on its header
// layout.
let moduleMapType: ModuleMapType
if self.fileSystem.exists(publicHeadersPath) {
let moduleMapGenerator = ModuleMapGenerator(
targetName: potentialModule.name,
moduleName: potentialModule.name.spm_mangledToC99ExtendedIdentifier(),
publicHeadersDir: publicHeadersPath,
fileSystem: self.fileSystem
)
moduleMapType = moduleMapGenerator.determineModuleMapType(observabilityScope: self.observabilityScope)
} else if moduleKind == .library, self.manifest.toolsVersion >= .v5_5 {
// If this clang target is a library, it must contain "include" directory.
throw ModuleError.invalidPublicHeadersDirectory(potentialModule.name)
} else {
moduleMapType = .none
}
if resources.contains(where: { $0.rule == .embedInCode }) {
throw ModuleError.embedInCodeNotSupported(target: potentialModule.name)
}
return try ClangModule(
name: potentialModule.name,
potentialBundleName: potentialBundleName,
cLanguageStandard: self.manifest.cLanguageStandard,
cxxLanguageStandard: self.manifest.cxxLanguageStandard,
includeDir: publicHeadersPath,
moduleMapType: moduleMapType,
headers: headers,
type: moduleKind,
path: potentialModule.path,
sources: sources,
resources: resources,
ignored: ignored,
dependencies: dependencies,
buildSettings: buildSettings,
buildSettingsDescription: manifestTarget.settings,
usesUnsafeFlags: manifestTarget.usesUnsafeFlags
)
}
}
/// Creates build setting assignment table for the given target.
func buildSettings(
for target: TargetDescription?,
targetRoot: AbsolutePath,
cxxLanguageStandard: String? = nil,
toolsSwiftVersion: SwiftLanguageVersion
) throws -> BuildSettings.AssignmentTable {
var table = BuildSettings.AssignmentTable()
guard let target else { return table }
// First let's add a default assignments for tools swift version.
var versionAssignment = BuildSettings.Assignment(default: true)
versionAssignment.values = [toolsSwiftVersion.rawValue]
table.add(versionAssignment, for: .SWIFT_VERSION)
// Process each setting.
for setting in target.settings {
let decl: BuildSettings.Declaration
let values: [String]
// Compute appropriate declaration for the setting.
switch setting.kind {
case .headerSearchPath(let value):
values = [value]
switch setting.tool {
case .c, .cxx:
decl = .HEADER_SEARCH_PATHS
case .swift, .linker:
throw InternalError("unexpected tool for setting type \(setting)")
}
// Ensure that the search path is contained within the package.
_ = try RelativePath(validating: value)
let path = try AbsolutePath(validating: value, relativeTo: targetRoot)
guard path.isDescendantOfOrEqual(to: self.packagePath) else {
throw ModuleError.invalidHeaderSearchPath(value)
}
case .define(let value):
values = [value]
switch setting.tool {
case .c, .cxx:
decl = .GCC_PREPROCESSOR_DEFINITIONS
case .swift:
decl = .SWIFT_ACTIVE_COMPILATION_CONDITIONS
case .linker:
throw InternalError("unexpected tool for setting type \(setting)")
}
case .linkedLibrary(let value):
values = [value]
switch setting.tool {
case .c, .cxx, .swift:
throw InternalError("unexpected tool for setting type \(setting)")
case .linker:
decl = .LINK_LIBRARIES
}
case .linkedFramework(let value):
values = [value]
switch setting.tool {
case .c, .cxx, .swift:
throw InternalError("unexpected tool for setting type \(setting)")
case .linker:
decl = .LINK_FRAMEWORKS
}
case .interoperabilityMode(let lang):
switch setting.tool {
case .c, .cxx, .linker:
throw InternalError("only Swift supports interoperability")
case .swift:
decl = .OTHER_SWIFT_FLAGS
}
if lang == .Cxx {
values = ["-cxx-interoperability-mode=default"] +
(cxxLanguageStandard.flatMap { ["-Xcc", "-std=\($0)"] } ?? [])
} else {
values = []
}
case .unsafeFlags(let _values):
values = _values
switch setting.tool {
case .c:
decl = .OTHER_CFLAGS
case .cxx:
decl = .OTHER_CPLUSPLUSFLAGS
case .swift:
decl = .OTHER_SWIFT_FLAGS
case .linker:
decl = .OTHER_LDFLAGS
}
case .enableUpcomingFeature(let value):
switch setting.tool {
case .c, .cxx, .linker:
throw InternalError("only Swift supports upcoming features")
case .swift:
decl = .OTHER_SWIFT_FLAGS
}
values = ["-enable-upcoming-feature", value]
case .enableExperimentalFeature(let value):
switch setting.tool {
case .c, .cxx, .linker:
throw InternalError(
"only Swift supports experimental features"
)
case .swift:
decl = .OTHER_SWIFT_FLAGS
}
values = ["-enable-experimental-feature", value]
case .swiftLanguageMode(let version):
switch setting.tool {
case .c, .cxx, .linker:
throw InternalError("only Swift supports swift language version")
case .swift:
decl = .SWIFT_VERSION
}
values = [version.rawValue]
}
// Create an assignment for this setting.
var assignment = BuildSettings.Assignment()
assignment.values = values
assignment.conditions = self.buildConditions(from: setting.condition)
// Finally, add the assignment to the assignment table.
table.add(assignment, for: decl)
}
return table
}
func buildConditions(from condition: PackageConditionDescription?) -> [PackageCondition] {
var conditions: [PackageCondition] = []
if let config = condition?.config.flatMap({ BuildConfiguration(rawValue: $0) }) {
conditions.append(.init(configuration: config))
}
if let platforms = condition?.platformNames.map({
if let platform = platformRegistry.platformByName[$0] {
platform
} else {
PackageModel.Platform.custom(name: $0, oldestSupportedVersion: .unknown)
}
}), !platforms.isEmpty {
conditions.append(.init(platforms: platforms))
}
return conditions
}
private func declaredSwiftVersions() throws -> [SwiftLanguageVersion] {
if let versions = self.declaredSwiftVersionsCache {
return versions
}
let versions: [SwiftLanguageVersion]
if let swiftLanguageVersions = manifest.swiftLanguageVersions {
versions = swiftLanguageVersions.sorted(by: >).filter { $0 <= ToolsVersion.current }
if versions.isEmpty {
throw ModuleError.incompatibleToolsVersions(
package: self.identity.description, required: swiftLanguageVersions, current: .current
)
}
} else {
versions = []
}
self.declaredSwiftVersionsCache = versions
return versions
}
/// Computes the swift version to use for this manifest.
private func toolsSwiftVersion() throws -> SwiftLanguageVersion {
if let swiftVersion = self.swiftVersionCache {
return swiftVersion
}
// Figure out the swift version from declared list in the manifest.
let declaredSwiftVersions = try declaredSwiftVersions()
let computedSwiftVersion: SwiftLanguageVersion = if let declaredSwiftVersion = declaredSwiftVersions.first {
declaredSwiftVersion
} else {
// Otherwise, use the version depending on the manifest version.
self.manifest.toolsVersion.swiftLanguageVersion
}
self.swiftVersionCache = computedSwiftVersion
return computedSwiftVersion
}
/// Validates that the sources of a target are not already present in another target.
private func validateSourcesOverlapping(forTarget target: String, sources: [AbsolutePath]) throws {
// Compute the sources which overlap with already computed targets.
var overlappingSources: Set<AbsolutePath> = []
for source in sources {
if !self.allSources.insert(source).inserted {
overlappingSources.insert(source)
}
}
// Throw if we found any overlapping sources.
if !overlappingSources.isEmpty {
throw ModuleError.overlappingSources(target: target, sources: Array(overlappingSources))
}
}
/// Find the test entry point file for the package.
private func findTestEntryPoint(in testTargets: [Module]) throws -> AbsolutePath? {
if let testEntryPointPath {
return testEntryPointPath
}
var testEntryPointFiles = Set<AbsolutePath>()
var pathsSearched = Set<AbsolutePath>()
// Look for entry point file adjacent to each test target root, iterating upto package root.
for target in testTargets {
// Form the initial search path.
//
// If the target root's parent directory is inside the package, start
// search there. Otherwise, we start search from the target root.
var searchPath = target.sources.root.parentDirectory
if !searchPath.isDescendantOfOrEqual(to: self.packagePath) {
searchPath = target.sources.root
}
while true {
guard searchPath.isDescendantOfOrEqual(to: self.packagePath) else {
throw InternalError("search path \(searchPath) is outside the package \(self.packagePath)")
}
// If we have already searched this path, skip.
if !pathsSearched.contains(searchPath) {
for name in SwiftModule.testEntryPointNames {
let path = searchPath.appending(component: name)
if self.fileSystem.isFile(path) {
testEntryPointFiles.insert(path)
}
}
pathsSearched.insert(searchPath)
}
// Break if we reached all the way to package root.
if searchPath == self.packagePath { break }
// Go one level up.
searchPath = searchPath.parentDirectory
}
}
// It is an error if there are multiple linux main files.
if testEntryPointFiles.count > 1 {
throw ModuleError.multipleTestEntryPointFilesFound(
package: self.identity.description, files: testEntryPointFiles.map { $0 }
)
}
return testEntryPointFiles.first
}
/// Collects the products defined by a package.
private func constructProducts(_ modules: [Module]) throws -> [Product] {
var products = OrderedCollections.OrderedSet<KeyedPair<Product, String>>()
/// Helper method to append to products array.
func append(_ product: Product) {
let inserted = products.append(KeyedPair(product, key: product.name)).inserted
if !inserted {
self.observabilityScope.emit(.duplicateProduct(product: product))
}
}
// Collect all test modules.
let testModules = modules.filter { module in
guard module.type == .test else { return false }
#if os(Linux)
// FIXME: Ignore C language test targets on linux for now.
if module is ClangModule {
self.observabilityScope
.emit(.unsupportedCTestTarget(package: self.identity.description, target: module.name))
return false
}
#endif
return true
}
// If enabled, create one test product for each test module.
if self.shouldCreateMultipleTestProducts {
for testModule in testModules {
let product = try Product(
package: self.identity,
name: testModule.name,
type: .test,
modules: [testModule]
)
append(product)
}
} else if !testModules.isEmpty {
// Otherwise we only need to create one test product for all of the
// test targets.
//
// Add suffix 'PackageTests' to test product name so the target name
// of linux executable don't collide with main package, if present.
// FIXME: use identity instead
let productName = self.manifest.displayName + "PackageTests"
let testEntryPointPath = try self.findTestEntryPoint(in: testModules)
let product = try Product(
package: self.identity,
name: productName,
type: .test,
modules: testModules,
testEntryPointPath: testEntryPointPath
)
append(product)
}
// Map containing modules mapped to their names.
let modulesMap = Dictionary(modules.map { ($0.name, $0) }, uniquingKeysWith: { $1 })
/// Helper method to get targets from target names.
func modulesFrom(moduleNames names: [String], product: String) throws -> [Module] {
// Get targets from target names.
try names.map { targetName in
// Ensure we have this target.
guard let target = modulesMap[targetName] else {
throw Product.Error.moduleEmpty(product: product, target: targetName)
}
return target
}
}
// First add explicit products.
let filteredProducts: [ProductDescription] = switch self.productFilter {
case .everything:
self.manifest.products
case .specific(let set):
self.manifest.products.filter { set.contains($0.name) }
}
for product in filteredProducts {
if product.name.isEmpty {
throw Product.Error.emptyName
}
let modules = try modulesFrom(moduleNames: product.targets, product: product.name)
// Perform special validations if this product is exporting
// a system library target.
if modules.contains(where: { $0 is SystemLibraryModule }) {
if product.type != .library(.automatic) || modules.count != 1 {
self.observabilityScope.emit(.systemPackageProductValidation(product: product.name))
continue
}
}
// Do some validation based on the product type.
switch product.type {
case .library:
guard self.validateLibraryProduct(product, with: modules) else {
continue
}
case .test, .macro:
break
case .executable, .snippet:
guard self.validateExecutableProduct(product, with: modules) else {
continue
}
case .plugin:
guard self.validatePluginProduct(product, with: modules) else {
continue
}
}
try append(Product(package: self.identity, name: product.name, type: product.type, modules: modules))
}
// Add implicit executables - for root packages and for dependency plugins.
// Compute the list of targets which are being used in an
// executable product so we don't create implicit executables
// for them.
let explicitProductsModules = Set(self.manifest.products.flatMap { product -> [String] in
switch product.type {
case .library, .plugin, .test, .macro:
return []
case .executable, .snippet:
return product.targets
}
})
let productMap = products.reduce(into: [String: Product]()) { partial, iterator in
partial[iterator.key] = iterator.item
}
let implicitPlugInExecutables = Set(
modules.lazy
.filter { $0.type == .plugin }
.flatMap(\.dependencies)
.map(\.name)
)
for module in modules where module.type == .executable {
if self.manifest.packageKind.isRoot && explicitProductsModules.contains(module.name) {
// If there is already an executable module with this name, skip generating a product for it
// (This shortcut only works for the root manifest, because for dependencies,
// products that correspond to plugāins may have been culled during resolution.)
continue
} else if let product = productMap[module.name] {
// If there is already a product with this name skip generating a product for it,
// but warn if that product is not executable
if product.type != .executable {
self.observabilityScope
.emit(
warning: "The target named '\(module.name)' was identified as an executable target but a non-executable product with this name already exists."
)
}
continue
} else {
if self.manifest.packageKind.isRoot || implicitPlugInExecutables.contains(module.name) {
// Generate an implicit product for the executable target
let product = try Product(
package: self.identity,
name: module.name,
type: .executable,
modules: [module]
)
append(product)
}
}
}
// Create a special REPL product that contains all the library targets.
if self.createREPLProduct {
let libraryTargets = modules.filter { $0.type == .library }
if libraryTargets.isEmpty {
self.observabilityScope.emit(.noLibraryTargetsForREPL)
} else {
let replProduct = try Product(
package: self.identity,
name: self.identity.description + Product.replProductSuffix,
type: .library(.dynamic),
modules: libraryTargets
)
append(replProduct)
}
}
// Create implicit snippet products
try modules
.filter { $0.type == .snippet }
.map { try Product(package: self.identity, name: $0.name, type: .snippet, modules: [$0]) }
.forEach(append)
// Create implicit macro products
try modules
.filter { $0.type == .macro }
.map { try Product(package: self.identity, name: $0.name, type: .macro, modules: [$0]) }
.forEach(append)
return products.map(\.item)
}
private func validateLibraryProduct(_ product: ProductDescription, with targets: [Module]) -> Bool {
let pluginTargets = targets.filter { $0.type == .plugin }
guard pluginTargets.isEmpty else {
self.observabilityScope.emit(.nonPluginProductWithPluginTargets(
product: product.name,
type: product.type,
pluginTargets: pluginTargets.map(\.name)
))
return false
}
if self.manifest.toolsVersion >= .v5_7 {
let executableTargets = targets.filter { $0.type == .executable }
guard executableTargets.isEmpty else {
self.observabilityScope
.emit(.libraryProductWithExecutableTarget(
product: product.name,
executableTargets: executableTargets.map(\.name)
))
return false
}
}
return true
}
private func validateExecutableProduct(_ product: ProductDescription, with targets: [Module]) -> Bool {
let executableTargetCount = targets.executables.count
guard executableTargetCount == 1 else {
if executableTargetCount == 0 {
if let target = targets.spm_only {
self.observabilityScope
.emit(.executableProductTargetNotExecutable(product: product.name, target: target.name))
} else {
self.observabilityScope.emit(.executableProductWithoutExecutableTarget(product: product.name))
}
} else {
self.observabilityScope.emit(.executableProductWithMoreThanOneExecutableTarget(product: product.name))
}
return false
}
let pluginTargets = targets.filter { $0.type == .plugin }
guard pluginTargets.isEmpty else {
self.observabilityScope.emit(.nonPluginProductWithPluginTargets(
product: product.name,
type: product.type,
pluginTargets: pluginTargets.map(\.name)
))
return false
}
return true
}
private func validatePluginProduct(_ product: ProductDescription, with targets: [Module]) -> Bool {
let nonPluginTargets = targets.filter { $0.type != .plugin }
guard nonPluginTargets.isEmpty else {
self.observabilityScope
.emit(.pluginProductWithNonPluginTargets(
product: product.name,
otherTargets: nonPluginTargets.map(\.name)
))
return false
}
guard !targets.isEmpty else {
self.observabilityScope.emit(.pluginProductWithNoTargets(product: product.name))
return false
}
return true
}
/// Returns the first suggested predefined source directory for a given target type.
public static func suggestedPredefinedSourceDirectory(type: TargetDescription.TargetKind) -> String {
// These are static constants, safe to access by index; the first choice is preferred.
switch type {
case .test:
self.predefinedTestDirectories[0]
case .plugin:
self.predefinedPluginDirectories[0]
default:
self.predefinedSourceDirectories[0]
}
}
}
extension PackageBuilder {
struct PredefinedTargetDirectory {
let path: AbsolutePath
let contents: [String]
init(fs: FileSystem, path: AbsolutePath) {
self.path = path
self.contents = (try? fs.getDirectoryContents(path)) ?? []
}
}
}
/// We create this structure after scanning the filesystem for potential modules.
private struct PotentialModule: Hashable {
/// Name of the module.
let name: String
/// The path of the module.
let path: AbsolutePath
/// If this should be a test module.
var isTest: Bool {
self.type == .test
}
/// The module type.
let type: TargetDescription.TargetKind
/// If true, access to package declarations from other modules is allowed.
let packageAccess: Bool
}
extension Manifest {
/// Returns the names of all the visible modules in the manifest.
fileprivate func visibleModuleNames(for productFilter: ProductFilter) -> Set<String> {
let names = targetsRequired(for: productFilter).flatMap { target in
[target.name] + target.dependencies.compactMap {
switch $0 {
case .target(let name, _):
name
case .byName, .product:
nil
}
}
}
return Set(names)
}
}
extension Sources {
var hasSwiftSources: Bool {
paths.contains { path in
guard let ext = path.extension else { return false }
return FileRuleDescription.swift.fileTypes.contains(ext)
}
}
var hasClangSources: Bool {
let supportedClangFileExtensions = FileRuleDescription.clang.fileTypes.union(FileRuleDescription.asm.fileTypes)
return paths.contains { path in
guard let ext = path.extension else { return false }
return supportedClangFileExtensions.contains(ext)
}
}
var containsMixedLanguage: Bool {
self.hasSwiftSources && self.hasClangSources
}
/// Determine module type based on the sources.
fileprivate func computeModuleKind() -> Module.Kind {
let isLibrary = !relativePaths.contains { path in
let file = path.basename.lowercased()
// Look for a main.xxx file avoiding cases like main.xxx.xxx
return file.hasPrefix("main.") && String(file.filter { $0 == "." }).count == 1
}
return isLibrary ? .library : .executable
}
}
extension Module.Dependency {
fileprivate var nameAndType: String {
switch self {
case .module:
"target-\(name)"
case .product:
"product-\(name)"
}
}
}
// MARK: - Snippets
extension PackageBuilder {
private func createSnippetModules(dependencies: [Module.Dependency]) throws -> [Module] {
let snippetsDirectory = self.packagePath.appending("Snippets")
guard self.fileSystem.isDirectory(snippetsDirectory) else {
return []
}
return try walk(snippetsDirectory, fileSystem: self.fileSystem)
.filter { self.fileSystem.isFile($0) && $0.extension == "swift" }
.map { sourceFile in
let name = sourceFile.basenameWithoutExt
let sources = Sources(paths: [sourceFile], root: sourceFile.parentDirectory)
let buildSettings: BuildSettings.AssignmentTable
let targetDescription = try TargetDescription(
name: name,
dependencies: dependencies
.map {
TargetDescription.Dependency.target(name: $0.name)
},
path: sourceFile.parentDirectory.pathString,
sources: [sourceFile.pathString],
type: .executable,
packageAccess: false
)
buildSettings = try self.buildSettings(
for: targetDescription,
targetRoot: sourceFile.parentDirectory,
toolsSwiftVersion: self.toolsSwiftVersion()
)
return SwiftModule(
name: name,
type: .snippet,
path: .root,
sources: sources,
dependencies: dependencies,
packageAccess: false,
buildSettings: buildSettings,
buildSettingsDescription: targetDescription.settings,
usesUnsafeFlags: false
)
}
}
}
extension Sequence {
/// Construct a new array where each of the elements in the \c self
/// sequence is preceded by the \c prefixElement.
///
/// For example:
/// ```
/// ["Alice", "Bob", "Charlie"].precedeElements(with: "Hi")
/// ```
///
/// produces `["Hi", "Alice", "Hi", "Bob", "Hi", "Charlie"]`.
private func precedeElements(with prefixElement: Element) -> [Element] {
var results: [Element] = []
for element in self {
results.append(prefixElement)
results.append(element)
}
return results
}
}
extension TargetDescription {
fileprivate var usesUnsafeFlags: Bool {
settings.filter(\.kind.isUnsafeFlags).isEmpty == false
}
}
|