@cdxgen/cdxgen 13.1.0 → 13.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -15
- package/bin/cdxgen.js +243 -122
- package/bin/evinse.js +7 -0
- package/bin/sign.js +88 -14
- package/bin/tracebom.js +64 -4
- package/bin/validate.js +29 -7
- package/bin/verify.js +79 -39
- package/data/crypto-oid.json +88 -12
- package/data/remediations.json +47 -0
- package/data/rules/ai-bom.yaml +78 -0
- package/data/rules/cargo-rust.yaml +132 -0
- package/data/rules/mcp-servers.yaml +122 -0
- package/lib/audit/targets.js +6 -1
- package/lib/cli/bomAssembly.js +117 -15
- package/lib/cli/cliOptions.js +93 -1
- package/lib/cli/index.js +235 -5
- package/lib/cli/jsBom.js +108 -26
- package/lib/cli/jvmBom.js +68 -12
- package/lib/cli/managedBom.js +264 -41
- package/lib/cli/nativeBom.js +923 -15
- package/lib/core/activity.js +120 -2
- package/lib/core/env.js +44 -7
- package/lib/core/fs.js +174 -6
- package/lib/core/metadataProperties.js +97 -0
- package/lib/core/projectConfig.js +31 -4
- package/lib/core/userAgent.js +151 -0
- package/lib/ecosystems/bunutils.js +269 -101
- package/lib/ecosystems/cargoCache.js +364 -0
- package/lib/ecosystems/caxa.js +447 -7
- package/lib/ecosystems/cmakeResolver.js +66 -0
- package/lib/ecosystems/cppEvidence.js +104 -33
- package/lib/ecosystems/ecosystems.js +260 -99
- package/lib/ecosystems/golem.js +11 -1
- package/lib/ecosystems/gradleutils.js +185 -9
- package/lib/ecosystems/jsEvidence.js +19 -20
- package/lib/ecosystems/kosi.js +1263 -0
- package/lib/ecosystems/npmutils.js +110 -9
- package/lib/ecosystems/parsers-crystal.js +179 -0
- package/lib/ecosystems/parsers-dotnet.js +120 -49
- package/lib/ecosystems/parsers-elm.js +816 -0
- package/lib/ecosystems/parsers-gleam.js +1 -1
- package/lib/ecosystems/parsers-go.js +47 -9
- package/lib/ecosystems/parsers-js.js +171 -50
- package/lib/ecosystems/parsers-julia.js +268 -0
- package/lib/ecosystems/parsers-lua.js +218 -0
- package/lib/ecosystems/parsers-misc.js +919 -161
- package/lib/ecosystems/parsers-nim.js +221 -0
- package/lib/ecosystems/parsers-opam.js +180 -0
- package/lib/ecosystems/parsers-perl.js +277 -0
- package/lib/ecosystems/parsers-python.js +27 -15
- package/lib/ecosystems/parsers-r.js +148 -0
- package/lib/ecosystems/parsers-rebar.js +411 -0
- package/lib/ecosystems/parsers-rust.js +954 -33
- package/lib/ecosystems/parsers-spack.js +204 -0
- package/lib/ecosystems/parsers-stack.js +264 -0
- package/lib/ecosystems/parsers-terraform.js +194 -0
- package/lib/ecosystems/rubyutils.js +1426 -166
- package/lib/ecosystems/rusi.js +158 -7
- package/lib/evinser/evinser.js +547 -129
- package/lib/evinser/swiftsem.js +1914 -302
- package/lib/helpers/agenticToolutils.js +925 -0
- package/lib/helpers/bomSigner.js +652 -221
- package/lib/inventory/agentFormulationParser.js +57 -4
- package/lib/inventory/aiCollector.js +504 -10
- package/lib/inventory/aiInventory.js +12 -0
- package/lib/inventory/analyzer.js +488 -30
- package/lib/inventory/astWorker.js +6 -0
- package/lib/inventory/atomUtils.js +568 -31
- package/lib/inventory/auditCategories.js +8 -0
- package/lib/inventory/cbomutils.js +30 -0
- package/lib/inventory/cdxrs.js +110 -14
- package/lib/inventory/communityAiConfigParser.js +215 -2
- package/lib/inventory/depsUtils.js +183 -11
- package/lib/inventory/display.js +4 -5
- package/lib/inventory/dosai.js +627 -13
- package/lib/inventory/dynamic.js +39 -1
- package/lib/inventory/envcontext.js +9 -6
- package/lib/inventory/evidenceUtils.js +26 -5
- package/lib/inventory/fetchBatch.js +190 -19
- package/lib/inventory/fetchRate.js +74 -11
- package/lib/inventory/forgeEnricher.js +3 -2
- package/lib/inventory/jvmToolEnv.js +6 -5
- package/lib/inventory/mcpConfigParser.js +166 -2
- package/lib/inventory/mcpDiscovery.js +11 -0
- package/lib/inventory/mcpServerManifest.js +389 -0
- package/lib/inventory/mcpbBundle.js +419 -0
- package/lib/inventory/plugins.js +3 -0
- package/lib/inventory/protobom.js +62 -89
- package/lib/inventory/protobomLoader.js +4 -0
- package/lib/inventory/purl.js +152 -3
- package/lib/inventory/rustFormulationParser.js +1 -1
- package/lib/inventory/sbomDocument.js +2 -70
- package/lib/inventory/source.js +47 -23
- package/lib/inventory/stylePackageRefs.js +41 -0
- package/lib/inventory/svelteUtils.js +524 -0
- package/lib/inventory/traceRunner.js +210 -21
- package/lib/managers/binary.js +115 -24
- package/lib/managers/docker.js +440 -20
- package/lib/managers/oci.js +39 -1
- package/lib/parsers/cmakeLists.js +452 -0
- package/lib/parsers/huggingfaceUtils.js +15 -7
- package/lib/parsers/largeJson.js +454 -0
- package/lib/parsers/npmrc.js +19 -1
- package/lib/parsers/onnx.js +294 -0
- package/lib/parsers/safetensors.js +114 -0
- package/lib/stages/postgen/introspection/markers.js +29 -6
- package/lib/stages/postgen/postgen.js +237 -16
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
- package/lib/validator/bomValidator.js +6 -16
- package/lib/validator/index.js +48 -10
- package/lib/validator/reporters/console.js +3 -0
- package/lib/validator/reporters/json.js +1 -0
- package/lib/validator/reporters/sarif.js +7 -0
- package/package.json +27 -27
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +2 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +33 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +2 -2
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +23 -0
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts +33 -0
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts +88 -8
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/activity.d.ts.map +1 -1
- package/types/lib/core/env.d.ts +21 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +55 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/metadataProperties.d.ts +39 -0
- package/types/lib/core/metadataProperties.d.ts.map +1 -0
- package/types/lib/core/projectConfig.d.ts +6 -2
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/userAgent.d.ts +56 -0
- package/types/lib/core/userAgent.d.ts.map +1 -0
- package/types/lib/ecosystems/bunutils.d.ts +12 -5
- package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
- package/types/lib/ecosystems/cargoCache.d.ts +75 -0
- package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
- package/types/lib/ecosystems/caxa.d.ts +14 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -1
- package/types/lib/ecosystems/cmakeResolver.d.ts +23 -0
- package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts +2 -2
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts +15 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +40 -4
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +86 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -0
- package/types/lib/ecosystems/npmutils.d.ts +42 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
- package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
- package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-go.d.ts +10 -0
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +10 -4
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
- package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-misc.d.ts +90 -3
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
- package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
- package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
- package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-r.d.ts +35 -0
- package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
- package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
- package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +143 -2
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts +13 -0
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +81 -2
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +280 -15
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/helpers/agenticToolutils.d.ts +87 -0
- package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
- package/types/lib/helpers/bomSigner.d.ts +106 -13
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/aiInventory.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/atomUtils.d.ts +145 -3
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +4 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/cdxrs.d.ts +8 -3
- package/types/lib/inventory/cdxrs.d.ts.map +1 -1
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts +15 -0
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +87 -1
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +34 -0
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/fetchRate.d.ts +9 -6
- package/types/lib/inventory/fetchRate.d.ts.map +1 -1
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +5 -4
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
- package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
- package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
- package/types/lib/inventory/mcpbBundle.d.ts +19 -0
- package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
- package/types/lib/inventory/plugins.d.ts.map +1 -1
- package/types/lib/inventory/protobom.d.ts +18 -13
- package/types/lib/inventory/protobom.d.ts.map +1 -1
- package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
- package/types/lib/inventory/purl.d.ts +71 -2
- package/types/lib/inventory/purl.d.ts.map +1 -1
- package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
- package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
- package/types/lib/inventory/svelteUtils.d.ts +124 -0
- package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
- package/types/lib/inventory/traceRunner.d.ts +47 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +30 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +29 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +14 -0
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/parsers/cmakeLists.d.ts +76 -0
- package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
- package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
- package/types/lib/parsers/largeJson.d.ts +52 -0
- package/types/lib/parsers/largeJson.d.ts.map +1 -0
- package/types/lib/parsers/npmrc.d.ts +5 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -1
- package/types/lib/parsers/onnx.d.ts +28 -0
- package/types/lib/parsers/onnx.d.ts.map +1 -0
- package/types/lib/parsers/safetensors.d.ts +22 -0
- package/types/lib/parsers/safetensors.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts +3 -2
- package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts +33 -0
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +10 -1
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
package/lib/evinser/swiftsem.js
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
1
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
2
|
+
import {
|
|
3
|
+
basename,
|
|
4
|
+
dirname,
|
|
5
|
+
isAbsolute,
|
|
6
|
+
join,
|
|
7
|
+
posix,
|
|
8
|
+
relative,
|
|
9
|
+
win32,
|
|
10
|
+
} from "node:path";
|
|
3
11
|
import process from "node:process";
|
|
4
12
|
|
|
5
13
|
import { DEBUG_MODE, readEnvironmentVariable } from "../core/activity.js";
|
|
6
|
-
import {
|
|
14
|
+
import { SWIFT_CMD } from "../core/env.js";
|
|
15
|
+
import {
|
|
16
|
+
getAllFiles,
|
|
17
|
+
getTmpDir,
|
|
18
|
+
safeExistsSync,
|
|
19
|
+
safeMkdtempSync,
|
|
20
|
+
safeRmSync,
|
|
21
|
+
safeSpawnSync,
|
|
22
|
+
} from "../core/fs.js";
|
|
7
23
|
import { splitCommandArgs } from "../ecosystems/parsers-js.js";
|
|
24
|
+
import { collectSwiftProductionTargets } from "../ecosystems/parsers-misc.js";
|
|
8
25
|
import { runSwiftCommand } from "../inventory/envcontext.js";
|
|
9
|
-
import { executeSourcekitten } from "../managers/binary.js";
|
|
26
|
+
import { executeSourcekitten, probeSourcekitten } from "../managers/binary.js";
|
|
10
27
|
|
|
11
28
|
// Swift entity kinds
|
|
12
29
|
// https://github.com/swiftlang/swift/blob/main/tools/SourceKit/docs/SwiftSupport.txt
|
|
@@ -70,15 +87,194 @@ const IGNORABLE_TYPES = [
|
|
|
70
87
|
"String",
|
|
71
88
|
];
|
|
72
89
|
|
|
90
|
+
// The Swift Build engine (default from Swift 6.4) writes per-target output
|
|
91
|
+
// file maps named `<target>-OutputFileMap.json` under
|
|
92
|
+
// `.build/out/Intermediates.noindex/`, while the llbuild engine writes
|
|
93
|
+
// `output-file-map.json` files next to the module artifacts.
|
|
94
|
+
const SWIFT_BUILD_FILEMAP_SUFFIX = "-OutputFileMap.json";
|
|
95
|
+
// SwiftPM turns target names into module names by replacing every character
|
|
96
|
+
// that is not valid in a C identifier with an underscore.
|
|
97
|
+
const NON_IDENTIFIER_CHARS = /[^A-Za-z0-9_]/g;
|
|
98
|
+
// Build logs and descriptions on Windows can mix both separators
|
|
99
|
+
const PATH_SEPARATORS = /[\\/]/;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Convert a SwiftPM target or package name into its module name.
|
|
103
|
+
*
|
|
104
|
+
* @param {String} name Target or package name
|
|
105
|
+
* @returns {String} Module name
|
|
106
|
+
*/
|
|
107
|
+
export function toModuleName(name) {
|
|
108
|
+
return (name || "").replace(NON_IDENTIFIER_CHARS, "_");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Decide whether a module belongs to tests or to an aggregate product and so
|
|
113
|
+
* must not contribute build symbols or semantic context.
|
|
114
|
+
*
|
|
115
|
+
* @param {String} moduleName Module name
|
|
116
|
+
* @returns {boolean} `true` when the module should be skipped
|
|
117
|
+
*/
|
|
118
|
+
export function isTestOrAggregateModule(moduleName) {
|
|
119
|
+
return (
|
|
120
|
+
!moduleName ||
|
|
121
|
+
moduleName.endsWith("Tests") ||
|
|
122
|
+
moduleName.endsWith("PackageTests") ||
|
|
123
|
+
moduleName.endsWith("_test_runner") ||
|
|
124
|
+
moduleName.endsWith("-test-runner") ||
|
|
125
|
+
moduleName.endsWith("_product") ||
|
|
126
|
+
moduleName.endsWith("-product")
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Spawn the configured swift command in a directory. `SWIFT_CMD` may carry a
|
|
132
|
+
* prefix such as `xcrun swift`.
|
|
133
|
+
*
|
|
134
|
+
* @param {String} basePath Working directory
|
|
135
|
+
* @param {Array<String>} args Arguments
|
|
136
|
+
* @param {Object} [spawnOptions] Extra spawn options, e.g. `input`
|
|
137
|
+
* @returns {Object} spawn result
|
|
138
|
+
*/
|
|
139
|
+
function spawnSwift(basePath, args, spawnOptions = {}) {
|
|
140
|
+
const [command, ...prefixArgs] = SWIFT_CMD.trim().split(" ");
|
|
141
|
+
return safeSpawnSync(command, [...prefixArgs, ...args], {
|
|
142
|
+
cwd: basePath,
|
|
143
|
+
...spawnOptions,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Parse the JSON printed by `swift -print-target-info`.
|
|
149
|
+
*
|
|
150
|
+
* `paths.runtimeResourcePath` is `<toolchain>/usr/lib/swift` for every
|
|
151
|
+
* toolchain layout (Xcode, `.xctoolchain` bundles, swiftly, Linux tarballs),
|
|
152
|
+
* so it names the toolchain that actually compiles the project.
|
|
153
|
+
*
|
|
154
|
+
* @param {Object} targetInfo Parsed `-print-target-info` output
|
|
155
|
+
* @returns {undefined|Object} `{ compilerVersion, triple, runtimeResourcePath, toolchainDir }`
|
|
156
|
+
*/
|
|
157
|
+
export function parseSwiftTargetInfo(targetInfo) {
|
|
158
|
+
const runtimeResourcePath = targetInfo?.paths?.runtimeResourcePath;
|
|
159
|
+
if (!runtimeResourcePath) {
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
const libDir = dirname(runtimeResourcePath);
|
|
163
|
+
const usrDir = dirname(libDir);
|
|
164
|
+
const toolchainDir =
|
|
165
|
+
basename(runtimeResourcePath) === "swift" &&
|
|
166
|
+
basename(libDir) === "lib" &&
|
|
167
|
+
basename(usrDir) === "usr"
|
|
168
|
+
? dirname(usrDir)
|
|
169
|
+
: undefined;
|
|
170
|
+
return {
|
|
171
|
+
compilerVersion: targetInfo.compilerVersion,
|
|
172
|
+
triple: targetInfo.target?.triple,
|
|
173
|
+
runtimeResourcePath,
|
|
174
|
+
toolchainDir,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const swiftToolchainCache = new Map();
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Identify the toolchain that `swift` resolves to inside a project directory.
|
|
182
|
+
*
|
|
183
|
+
* The query runs in the project directory because toolchain managers such as
|
|
184
|
+
* swiftly select the toolchain per directory (`.swift-version`), so the
|
|
185
|
+
* compiler that builds a project can differ from the one on the PATH of the
|
|
186
|
+
* calling process.
|
|
187
|
+
*
|
|
188
|
+
* @param {String} basePath Project directory
|
|
189
|
+
* @returns {undefined|Object} Toolchain details, see parseSwiftTargetInfo
|
|
190
|
+
*/
|
|
191
|
+
export function resolveSwiftToolchain(basePath) {
|
|
192
|
+
const cacheKey = basePath || process.cwd();
|
|
193
|
+
if (swiftToolchainCache.has(cacheKey)) {
|
|
194
|
+
return swiftToolchainCache.get(cacheKey);
|
|
195
|
+
}
|
|
196
|
+
let toolchain;
|
|
197
|
+
const result = spawnSwift(basePath, ["-print-target-info"]);
|
|
198
|
+
if (result?.status === 0 && result.stdout) {
|
|
199
|
+
try {
|
|
200
|
+
toolchain = parseSwiftTargetInfo(JSON.parse(result.stdout));
|
|
201
|
+
} catch (_e) {
|
|
202
|
+
toolchain = undefined;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
swiftToolchainCache.set(cacheKey, toolchain);
|
|
206
|
+
return toolchain;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Environment overrides that make sourcekitten load the SourceKit of the
|
|
211
|
+
* toolchain that builds the project.
|
|
212
|
+
*
|
|
213
|
+
* Without guidance sourcekitten uses the toolchain of the selected Xcode (or
|
|
214
|
+
* the `swift` found on the PATH on Linux). SourceKit cannot load modules
|
|
215
|
+
* produced by a different compiler version, so every reference to a
|
|
216
|
+
* dependency then goes unresolved. On macOS sourcekitten honours
|
|
217
|
+
* `XCODE_DEFAULT_TOOLCHAIN_OVERRIDE` ahead of `xcrun`; on Linux it honours
|
|
218
|
+
* `LINUX_SOURCEKIT_LIB_PATH`. Explicitly configured values always win.
|
|
219
|
+
*
|
|
220
|
+
* @param {Object} toolchain Toolchain details from resolveSwiftToolchain
|
|
221
|
+
* @param {String} [platform] Platform name, defaults to process.platform
|
|
222
|
+
* @returns {undefined|Object} Environment overrides for sourcekitten
|
|
223
|
+
*/
|
|
224
|
+
export function sourcekittenEnvForToolchain(
|
|
225
|
+
toolchain,
|
|
226
|
+
platform = process.platform,
|
|
227
|
+
) {
|
|
228
|
+
const toolchainDir = toolchain?.toolchainDir;
|
|
229
|
+
if (!toolchainDir) {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
if (platform === "darwin") {
|
|
233
|
+
if (
|
|
234
|
+
readEnvironmentVariable("XCODE_DEFAULT_TOOLCHAIN_OVERRIDE") ||
|
|
235
|
+
readEnvironmentVariable("TOOLCHAIN_DIR")
|
|
236
|
+
) {
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
// sourcekitten loads the in-process SourceKit of `.xctoolchain` bundles
|
|
240
|
+
// and of the Command Line Tools alike. Open-source toolchains before
|
|
241
|
+
// Swift 6.1 do not ship it; sourcekitten then falls back to the Xcode
|
|
242
|
+
// toolchain, which cannot load the modules of this build
|
|
243
|
+
if (
|
|
244
|
+
!safeExistsSync(
|
|
245
|
+
join(toolchainDir, "usr", "lib", "sourcekitdInProc.framework"),
|
|
246
|
+
)
|
|
247
|
+
) {
|
|
248
|
+
console.log(
|
|
249
|
+
`The Swift toolchain at ${toolchainDir} has no sourcekitdInProc.framework, so sourcekitten cannot use it. Swift evidence will be limited to import declarations; build with Swift 6.1 or later, or with Xcode.`,
|
|
250
|
+
);
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
return { XCODE_DEFAULT_TOOLCHAIN_OVERRIDE: toolchainDir };
|
|
254
|
+
}
|
|
255
|
+
if (platform === "linux") {
|
|
256
|
+
const libDir = join(toolchainDir, "usr", "lib");
|
|
257
|
+
if (
|
|
258
|
+
readEnvironmentVariable("LINUX_SOURCEKIT_LIB_PATH") ||
|
|
259
|
+
!safeExistsSync(join(libDir, "libsourcekitdInProc.so"))
|
|
260
|
+
) {
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
return { LINUX_SOURCEKIT_LIB_PATH: libDir };
|
|
264
|
+
}
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
73
268
|
/**
|
|
74
269
|
* Retrieve the structure information of a .swift file in json format
|
|
75
270
|
*
|
|
76
271
|
* @param {String} filePath Path to .swift file
|
|
272
|
+
* @param {Object} [env] Environment overrides for sourcekitten
|
|
77
273
|
*
|
|
78
274
|
* @returns {undefined|Object} JSON representation of the swift file or undefined.
|
|
79
275
|
*/
|
|
80
|
-
export function getStructure(filePath) {
|
|
81
|
-
return executeSourcekitten(["structure", "--file", filePath]);
|
|
276
|
+
export function getStructure(filePath, env) {
|
|
277
|
+
return executeSourcekitten(["structure", "--file", filePath], env);
|
|
82
278
|
}
|
|
83
279
|
|
|
84
280
|
/**
|
|
@@ -139,214 +335,599 @@ function collectStructureTypes(substructures, refTypes) {
|
|
|
139
335
|
}
|
|
140
336
|
}
|
|
141
337
|
|
|
338
|
+
/**
|
|
339
|
+
* Split a Swift command line, as printed by a verbose build or given in an
|
|
340
|
+
* environment variable, into arguments.
|
|
341
|
+
*
|
|
342
|
+
* Paths on Windows use backslash separators, so a backslash there is literal
|
|
343
|
+
* unless it precedes a double quote (the `CommandLineToArgvW` rules). Single
|
|
344
|
+
* quotes, which SwiftPM uses to quote arguments on every platform, keep their
|
|
345
|
+
* content literal. Elsewhere shell quoting applies.
|
|
346
|
+
*
|
|
347
|
+
* @param {String} commandString Command line
|
|
348
|
+
* @param {String} [platform] Platform name, defaults to process.platform
|
|
349
|
+
* @returns {Array<String>} Arguments
|
|
350
|
+
*/
|
|
351
|
+
export function splitSwiftCommandArgs(
|
|
352
|
+
commandString,
|
|
353
|
+
platform = process.platform,
|
|
354
|
+
) {
|
|
355
|
+
const text = commandString || "";
|
|
356
|
+
if (platform !== "win32") {
|
|
357
|
+
return splitCommandArgs(text);
|
|
358
|
+
}
|
|
359
|
+
const args = [];
|
|
360
|
+
let current = "";
|
|
361
|
+
let hasArg = false;
|
|
362
|
+
let quote = "";
|
|
363
|
+
for (let i = 0; i < text.length; i++) {
|
|
364
|
+
const char = text[i];
|
|
365
|
+
if (quote === "'") {
|
|
366
|
+
if (char === "'") {
|
|
367
|
+
quote = "";
|
|
368
|
+
} else {
|
|
369
|
+
current += char;
|
|
370
|
+
}
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (char === "\\") {
|
|
374
|
+
let count = 1;
|
|
375
|
+
while (text[i + count] === "\\") {
|
|
376
|
+
count++;
|
|
377
|
+
}
|
|
378
|
+
hasArg = true;
|
|
379
|
+
if (text[i + count] !== '"') {
|
|
380
|
+
current += "\\".repeat(count);
|
|
381
|
+
i += count - 1;
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
current += "\\".repeat(Math.floor(count / 2));
|
|
385
|
+
if (count % 2) {
|
|
386
|
+
current += '"';
|
|
387
|
+
i += count;
|
|
388
|
+
} else {
|
|
389
|
+
i += count - 1;
|
|
390
|
+
}
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
if (char === '"' || (char === "'" && !quote)) {
|
|
394
|
+
quote = quote ? "" : char;
|
|
395
|
+
hasArg = true;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (!quote && (char === " " || char === "\t")) {
|
|
399
|
+
if (hasArg) {
|
|
400
|
+
args.push(current);
|
|
401
|
+
current = "";
|
|
402
|
+
hasArg = false;
|
|
403
|
+
}
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
current += char;
|
|
407
|
+
hasArg = true;
|
|
408
|
+
}
|
|
409
|
+
if (hasArg) {
|
|
410
|
+
args.push(current);
|
|
411
|
+
}
|
|
412
|
+
return args;
|
|
413
|
+
}
|
|
414
|
+
|
|
142
415
|
/**
|
|
143
416
|
* Method to perform swift build in verbose mode.
|
|
144
417
|
*
|
|
418
|
+
* `SWIFT_BUILD_ARGS` adds arguments to the build, for example
|
|
419
|
+
* `--build-system native` to use the llbuild engine with Swift 6.4.
|
|
420
|
+
*
|
|
145
421
|
* @param {String} basePath Path
|
|
146
422
|
* @returns {undefined|String} Verbose build output
|
|
147
423
|
*/
|
|
148
424
|
export function verboseBuild(basePath) {
|
|
149
425
|
runSwiftCommand(basePath, ["package", "clean"]);
|
|
150
426
|
console.log("Extracting compiler arguments from swift build...");
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
"-Wno-error",
|
|
158
|
-
]);
|
|
427
|
+
const buildArgs = ["build", "-c", "debug", "--verbose", "-Xcc", "-Wno-error"];
|
|
428
|
+
const extraBuildArgs = readEnvironmentVariable("SWIFT_BUILD_ARGS");
|
|
429
|
+
if (extraBuildArgs) {
|
|
430
|
+
buildArgs.push(...splitSwiftCommandArgs(extraBuildArgs));
|
|
431
|
+
}
|
|
432
|
+
return runSwiftCommand(basePath, buildArgs);
|
|
159
433
|
}
|
|
160
434
|
|
|
435
|
+
// Compiler arguments followed by a single value that sourcekitten needs to
|
|
436
|
+
// type-check a module's sources exactly as the compiler did
|
|
437
|
+
const SWIFT_ARGS_WITH_VALUE = new Set([
|
|
438
|
+
"-sdk",
|
|
439
|
+
"-target",
|
|
440
|
+
"-target-variant",
|
|
441
|
+
"-swift-version",
|
|
442
|
+
"-language-mode",
|
|
443
|
+
"-module-name",
|
|
444
|
+
"-package-name",
|
|
445
|
+
"-I",
|
|
446
|
+
"-F",
|
|
447
|
+
"-Isystem",
|
|
448
|
+
"-Fsystem",
|
|
449
|
+
"-vfsoverlay",
|
|
450
|
+
"-plugin-path",
|
|
451
|
+
"-external-plugin-path",
|
|
452
|
+
"-load-plugin-executable",
|
|
453
|
+
"-load-plugin-library",
|
|
454
|
+
"-in-process-plugin-server-path",
|
|
455
|
+
"-enable-upcoming-feature",
|
|
456
|
+
"-enable-experimental-feature",
|
|
457
|
+
"-disable-upcoming-feature",
|
|
458
|
+
"-disable-experimental-feature",
|
|
459
|
+
"-Xcc",
|
|
460
|
+
]);
|
|
461
|
+
// Compiler arguments that apply to the whole module, typically single-value
|
|
462
|
+
// flags whose value appears once per line (`-module-cache-path` is replaced by
|
|
463
|
+
// a private cache)
|
|
464
|
+
const SWIFT_SINGLE_VALUE_ARGS = new Set([
|
|
465
|
+
"-sdk",
|
|
466
|
+
"-target",
|
|
467
|
+
"-target-variant",
|
|
468
|
+
"-swift-version",
|
|
469
|
+
"-language-mode",
|
|
470
|
+
"-module-name",
|
|
471
|
+
"-package-name",
|
|
472
|
+
]);
|
|
473
|
+
const SWIFT_BOOL_ARGS = new Set([
|
|
474
|
+
"-parse-as-library",
|
|
475
|
+
"-enable-testing",
|
|
476
|
+
"-application-extension",
|
|
477
|
+
"-enable-library-evolution",
|
|
478
|
+
"-enable-bare-slash-regex",
|
|
479
|
+
"-warn-concurrency",
|
|
480
|
+
"-suppress-warnings",
|
|
481
|
+
]);
|
|
482
|
+
// Frontend flags that change how sources are type-checked (macros, language
|
|
483
|
+
// features) and so are kept when passed through `-Xfrontend`
|
|
484
|
+
const SWIFT_FRONTEND_ARGS_WITH_VALUE = new Set([
|
|
485
|
+
"-load-plugin-executable",
|
|
486
|
+
"-load-plugin-library",
|
|
487
|
+
"-plugin-path",
|
|
488
|
+
"-external-plugin-path",
|
|
489
|
+
"-enable-upcoming-feature",
|
|
490
|
+
"-enable-experimental-feature",
|
|
491
|
+
]);
|
|
492
|
+
// Pass-through flags whose value belongs to another tool
|
|
493
|
+
const SWIFT_PASSTHROUGH_ARGS = new Set([
|
|
494
|
+
"-Xfrontend",
|
|
495
|
+
"-Xllvm",
|
|
496
|
+
"-Xlinker",
|
|
497
|
+
"-Xclang-linker",
|
|
498
|
+
]);
|
|
499
|
+
|
|
161
500
|
/**
|
|
162
|
-
*
|
|
501
|
+
* Reduce one module's compiler invocation to the arguments that affect how
|
|
502
|
+
* its sources are type-checked.
|
|
163
503
|
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
504
|
+
* Only known argument classes are kept, so outputs, incremental build
|
|
505
|
+
* bookkeeping, response files, and flags a SourceKit build may not know are
|
|
506
|
+
* dropped. The order of the kept arguments is preserved, which keeps paired
|
|
507
|
+
* clang arguments such as `-Xcc -I -Xcc <path>` intact. The module cache is
|
|
508
|
+
* dropped as well: the build's cache may hold modules of other toolchains.
|
|
509
|
+
*
|
|
510
|
+
* @param {Array<String>} tokens Tokenized compiler invocation
|
|
511
|
+
* @returns {Array<String>} Sanitized compiler arguments
|
|
166
512
|
*/
|
|
167
|
-
export function
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
"-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
"-I",
|
|
183
|
-
"-L",
|
|
184
|
-
"-vfsoverlay",
|
|
185
|
-
"-Xllvm",
|
|
186
|
-
"-external-plugin-path",
|
|
187
|
-
"-plugin-path",
|
|
188
|
-
]);
|
|
189
|
-
const orderedArgs = new Set(["-Xcc"]);
|
|
190
|
-
const boolArgs = new Set([
|
|
191
|
-
"-parse-as-library",
|
|
192
|
-
"-incremental",
|
|
193
|
-
"-track-system-dependencies",
|
|
194
|
-
"-suppress-remarks",
|
|
195
|
-
"-suppress-warnings",
|
|
196
|
-
"-stack-check",
|
|
197
|
-
"-disable-clang-spi",
|
|
198
|
-
"-no-color-diagnostics",
|
|
199
|
-
"-enable-testing",
|
|
200
|
-
"-enable-library-evolution",
|
|
201
|
-
]);
|
|
202
|
-
const ignoredArgs = new Set([
|
|
203
|
-
"-o",
|
|
204
|
-
"-output-file-map",
|
|
205
|
-
"-emit-module-path",
|
|
206
|
-
"-emit-module-doc-path",
|
|
207
|
-
"-emit-dependencies-path",
|
|
208
|
-
"-emit-reference-dependencies-path",
|
|
209
|
-
"-emit-objc-header-path",
|
|
210
|
-
"-primary-file",
|
|
211
|
-
"-main-file",
|
|
212
|
-
"-num-threads",
|
|
213
|
-
"-enable-objc-interop",
|
|
214
|
-
"-empty-abi-descriptor",
|
|
215
|
-
"-target-sdk-version",
|
|
216
|
-
"-target-sdk-name",
|
|
217
|
-
"-incremental",
|
|
218
|
-
"-module-name",
|
|
219
|
-
"-j",
|
|
220
|
-
]);
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Validates a parameter value to ensure it's not a garbage string or full command.
|
|
224
|
-
*/
|
|
225
|
-
const isValidParamValue = (val) => {
|
|
226
|
-
if (!val) return false;
|
|
227
|
-
if (val.length > 2048) return false;
|
|
228
|
-
return !(val.includes(" -cc1") || val.includes('clang"'));
|
|
513
|
+
export function sanitizeSwiftModuleArgs(tokens) {
|
|
514
|
+
const args = [];
|
|
515
|
+
const seenPairs = new Set();
|
|
516
|
+
const seenSingles = new Set();
|
|
517
|
+
const pushPair = (flag, value) => {
|
|
518
|
+
// Repeated include paths are harmless but noisy; `-Xcc` values are
|
|
519
|
+
// positional (`-Xcc -I -Xcc <path>`) and must never be de-duplicated
|
|
520
|
+
if (flag !== "-Xcc") {
|
|
521
|
+
const key = `${flag}\u0000${value}`;
|
|
522
|
+
if (seenPairs.has(key)) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
seenPairs.add(key);
|
|
526
|
+
}
|
|
527
|
+
args.push(flag, value);
|
|
229
528
|
};
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
529
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
530
|
+
const token = tokens[i];
|
|
531
|
+
if (SWIFT_PASSTHROUGH_ARGS.has(token)) {
|
|
532
|
+
const value = tokens[i + 1];
|
|
533
|
+
i++;
|
|
534
|
+
if (token !== "-Xfrontend" || !value) {
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
if (SWIFT_FRONTEND_ARGS_WITH_VALUE.has(value)) {
|
|
538
|
+
if (tokens[i + 1] === "-Xfrontend" && tokens[i + 2] !== undefined) {
|
|
539
|
+
args.push("-Xfrontend", value, "-Xfrontend", tokens[i + 2]);
|
|
540
|
+
i += 2;
|
|
541
|
+
}
|
|
542
|
+
} else if (value.startsWith("-strict-concurrency=")) {
|
|
543
|
+
args.push("-Xfrontend", value);
|
|
544
|
+
}
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
if (SWIFT_ARGS_WITH_VALUE.has(token)) {
|
|
548
|
+
const value = tokens[i + 1];
|
|
549
|
+
i++;
|
|
550
|
+
if (value === undefined) {
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
if (SWIFT_SINGLE_VALUE_ARGS.has(token)) {
|
|
554
|
+
if (seenSingles.has(token)) {
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
seenSingles.add(token);
|
|
558
|
+
}
|
|
559
|
+
pushPair(token, value);
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
if (SWIFT_BOOL_ARGS.has(token)) {
|
|
563
|
+
if (!seenSingles.has(token)) {
|
|
564
|
+
seenSingles.add(token);
|
|
565
|
+
args.push(token);
|
|
566
|
+
}
|
|
236
567
|
continue;
|
|
237
568
|
}
|
|
238
|
-
if (
|
|
569
|
+
if (token.startsWith("-strict-concurrency=")) {
|
|
570
|
+
args.push(token);
|
|
239
571
|
continue;
|
|
240
572
|
}
|
|
241
|
-
if (
|
|
242
|
-
|
|
573
|
+
if (token === "-D" && tokens[i + 1] !== undefined) {
|
|
574
|
+
pushPair("-D", tokens[i + 1]);
|
|
575
|
+
i++;
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
// Joined forms: -DFOO, -I/path, -F/path
|
|
579
|
+
if (token.length > 2 && ["-D", "-I", "-F"].includes(token.slice(0, 2))) {
|
|
580
|
+
if (!token.startsWith("-Isystem") && !token.startsWith("-Fsystem")) {
|
|
581
|
+
pushPair(token.slice(0, 2), token.slice(2));
|
|
582
|
+
}
|
|
243
583
|
}
|
|
244
584
|
}
|
|
585
|
+
return args;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Read the sources listed in a response or file-list file (`@<path>`), one
|
|
590
|
+
* path per line.
|
|
591
|
+
*
|
|
592
|
+
* @param {String} listFile File list path
|
|
593
|
+
* @param {String} [platform] Platform that wrote the list
|
|
594
|
+
* @returns {Array<String>} Swift source paths
|
|
595
|
+
*/
|
|
596
|
+
function readSourceList(listFile, platform = process.platform) {
|
|
597
|
+
if (!listFile || !safeExistsSync(listFile)) {
|
|
598
|
+
return [];
|
|
599
|
+
}
|
|
600
|
+
try {
|
|
601
|
+
return readFileSync(listFile, { encoding: "utf-8" })
|
|
602
|
+
.split("\n")
|
|
603
|
+
.map((l) => splitSwiftCommandArgs(l.trim(), platform)[0])
|
|
604
|
+
.filter((l) => l?.endsWith(".swift"));
|
|
605
|
+
} catch (_e) {
|
|
606
|
+
return [];
|
|
607
|
+
}
|
|
608
|
+
}
|
|
245
609
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
610
|
+
/**
|
|
611
|
+
* Extract the per-module compiler invocations printed by a verbose swift
|
|
612
|
+
* build.
|
|
613
|
+
*
|
|
614
|
+
* Both engines print one driver invocation per module that carries
|
|
615
|
+
* `-module-name`: llbuild prints `swiftc ... -module-name X ... @sources`,
|
|
616
|
+
* while the Swift Build engine prints `builtin-SwiftDriver -- swiftc ...
|
|
617
|
+
* -module-name X ... @<target>.SwiftFileList`. llbuild also prints the
|
|
618
|
+
* frontend jobs of each module, which are only used when no driver line is
|
|
619
|
+
* found. Package manifest and plugin compilations are skipped.
|
|
620
|
+
*
|
|
621
|
+
* @param {String} buildOutput Verbose build output
|
|
622
|
+
* @param {String} [platform] Platform that printed the output, which decides
|
|
623
|
+
* its quoting rules; defaults to process.platform
|
|
624
|
+
* @returns {Object} Map of module name to `{ args, sources, isDriver }`
|
|
625
|
+
*/
|
|
626
|
+
export function parseSwiftModuleInvocations(
|
|
627
|
+
buildOutput,
|
|
628
|
+
platform = process.platform,
|
|
629
|
+
) {
|
|
630
|
+
const isAbsolutePath =
|
|
631
|
+
platform === "win32" ? win32.isAbsolute : posix.isAbsolute;
|
|
632
|
+
const modules = {};
|
|
633
|
+
for (const rawLine of (buildOutput || "").split("\n")) {
|
|
634
|
+
const line = rawLine.trim();
|
|
635
|
+
if (!line.includes("swiftc") && !line.includes("swift-frontend")) {
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
if (!line.includes("-module-name")) {
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
const tokens = splitSwiftCommandArgs(line, platform);
|
|
642
|
+
const nameIndex = tokens.indexOf("-module-name");
|
|
643
|
+
const moduleName = tokens[nameIndex + 1];
|
|
644
|
+
if (
|
|
645
|
+
!moduleName ||
|
|
646
|
+
tokens.includes("-package-description-version") ||
|
|
647
|
+
tokens.includes("-plugin-description-version")
|
|
648
|
+
) {
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
const isDriver = !tokens.includes("-frontend");
|
|
652
|
+
const sources = new Set();
|
|
653
|
+
for (const token of tokens) {
|
|
654
|
+
if (token.startsWith("@")) {
|
|
655
|
+
for (const src of readSourceList(token.slice(1), platform)) {
|
|
656
|
+
sources.add(src);
|
|
254
657
|
}
|
|
658
|
+
} else if (token.endsWith(".swift") && isAbsolutePath(token)) {
|
|
659
|
+
sources.add(token);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
const existing = modules[moduleName];
|
|
663
|
+
// Prefer the driver invocation, then the longest frontend job
|
|
664
|
+
if (
|
|
665
|
+
existing &&
|
|
666
|
+
(existing.isDriver > isDriver ||
|
|
667
|
+
(existing.isDriver === isDriver && existing.length >= tokens.length))
|
|
668
|
+
) {
|
|
669
|
+
for (const src of sources) {
|
|
670
|
+
if (!existing.sources.includes(src)) {
|
|
671
|
+
existing.sources.push(src);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
const mergedSources = new Set([...(existing?.sources || []), ...sources]);
|
|
677
|
+
modules[moduleName] = {
|
|
678
|
+
args: sanitizeSwiftModuleArgs(tokens),
|
|
679
|
+
sources: Array.from(mergedSources),
|
|
680
|
+
isDriver,
|
|
681
|
+
length: tokens.length,
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
for (const amodule of Object.values(modules)) {
|
|
685
|
+
delete amodule.length;
|
|
686
|
+
}
|
|
687
|
+
return modules;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Parse the llbuild engine's build description
|
|
692
|
+
* (`.build/<triple>/debug/description.json`).
|
|
693
|
+
*
|
|
694
|
+
* Its `swiftCommands` record every Swift module the build compiled, with the
|
|
695
|
+
* exact source list and compiler arguments. Host-tool variants
|
|
696
|
+
* (`...-tool.module`, built for macros and plugins) are used only when a
|
|
697
|
+
* module has no regular variant.
|
|
698
|
+
*
|
|
699
|
+
* @param {Object} descriptionJson Parsed description.json
|
|
700
|
+
* @returns {Object} Map of module name to `{ args, sources, isDriver }`
|
|
701
|
+
*/
|
|
702
|
+
export function parseSwiftBuildDescription(descriptionJson) {
|
|
703
|
+
const modules = {};
|
|
704
|
+
const toolVariants = new Set();
|
|
705
|
+
for (const [key, command] of Object.entries(
|
|
706
|
+
descriptionJson?.swiftCommands || {},
|
|
707
|
+
)) {
|
|
708
|
+
const moduleName = command?.moduleName;
|
|
709
|
+
if (!moduleName) {
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
const isTool = key.endsWith("-tool.module");
|
|
713
|
+
if (modules[moduleName] && (isTool || !toolVariants.has(moduleName))) {
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
const tokens = [
|
|
717
|
+
"-module-name",
|
|
718
|
+
moduleName,
|
|
719
|
+
...(command.otherArguments || []),
|
|
720
|
+
];
|
|
721
|
+
if (command.importPath) {
|
|
722
|
+
tokens.push("-I", command.importPath);
|
|
723
|
+
}
|
|
724
|
+
modules[moduleName] = {
|
|
725
|
+
args: sanitizeSwiftModuleArgs(tokens),
|
|
726
|
+
sources: (command.sources || []).filter((s) => s.endsWith(".swift")),
|
|
727
|
+
isDriver: true,
|
|
728
|
+
};
|
|
729
|
+
if (isTool) {
|
|
730
|
+
toolVariants.add(moduleName);
|
|
731
|
+
} else {
|
|
732
|
+
toolVariants.delete(moduleName);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return modules;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Clang argument groups: a flag and the separate value that follows it
|
|
739
|
+
const CLANG_ARGS_WITH_SEPARATE_VALUE = new Set([
|
|
740
|
+
"-I",
|
|
741
|
+
"-F",
|
|
742
|
+
"-D",
|
|
743
|
+
"-U",
|
|
744
|
+
"-isystem",
|
|
745
|
+
"-iquote",
|
|
746
|
+
"-idirafter",
|
|
747
|
+
"-include",
|
|
748
|
+
"-iframework",
|
|
749
|
+
"-ivfsoverlay",
|
|
750
|
+
"-working-directory",
|
|
751
|
+
"-Xclang",
|
|
752
|
+
"-target",
|
|
753
|
+
]);
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Split a list of `-Xcc` values into clang argument groups so that repeated
|
|
757
|
+
* groups can be dropped without separating a flag from its value.
|
|
758
|
+
*
|
|
759
|
+
* @param {Array<String>} values `-Xcc` values in order
|
|
760
|
+
* @returns {Array<Array<String>>} Clang argument groups
|
|
761
|
+
*/
|
|
762
|
+
function groupClangArgs(values) {
|
|
763
|
+
const groups = [];
|
|
764
|
+
for (let i = 0; i < values.length; i++) {
|
|
765
|
+
if (
|
|
766
|
+
CLANG_ARGS_WITH_SEPARATE_VALUE.has(values[i]) &&
|
|
767
|
+
i + 1 < values.length
|
|
768
|
+
) {
|
|
769
|
+
groups.push([values[i], values[i + 1]]);
|
|
770
|
+
i++;
|
|
771
|
+
} else {
|
|
772
|
+
groups.push([values[i]]);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return groups;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* Merge the sanitized arguments of several modules into one argument list,
|
|
780
|
+
* used when a source file cannot be attributed to a single module. The first
|
|
781
|
+
* module's single-value flags win, include paths accumulate, and `-Xcc`
|
|
782
|
+
* values are merged per clang argument group.
|
|
783
|
+
*
|
|
784
|
+
* @param {Array<Array<String>>} argLists Sanitized argument lists, most relevant first
|
|
785
|
+
* @returns {Array<String>} Merged arguments
|
|
786
|
+
*/
|
|
787
|
+
export function mergeSwiftModuleArgs(argLists) {
|
|
788
|
+
const merged = [];
|
|
789
|
+
const seen = new Set();
|
|
790
|
+
const seenSingles = new Set();
|
|
791
|
+
const clangGroups = [];
|
|
792
|
+
const seenClangGroups = new Set();
|
|
793
|
+
for (const args of argLists) {
|
|
794
|
+
const xccValues = [];
|
|
795
|
+
for (let i = 0; i < args.length; i++) {
|
|
796
|
+
const token = args[i];
|
|
797
|
+
if (token === "-Xcc") {
|
|
798
|
+
xccValues.push(args[i + 1]);
|
|
799
|
+
i++;
|
|
255
800
|
continue;
|
|
256
801
|
}
|
|
257
|
-
if (
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
i++;
|
|
802
|
+
if (SWIFT_ARGS_WITH_VALUE.has(token) || token === "-D") {
|
|
803
|
+
const value = args[i + 1];
|
|
804
|
+
i++;
|
|
805
|
+
if (token === "-module-name") {
|
|
806
|
+
continue;
|
|
264
807
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (isValidParamValue(val)) {
|
|
269
|
-
if (!params[token]) {
|
|
270
|
-
params[token] = [];
|
|
271
|
-
}
|
|
272
|
-
params[token].push(val);
|
|
808
|
+
if (SWIFT_SINGLE_VALUE_ARGS.has(token)) {
|
|
809
|
+
if (seenSingles.has(token)) {
|
|
810
|
+
continue;
|
|
273
811
|
}
|
|
274
|
-
|
|
812
|
+
seenSingles.add(token);
|
|
275
813
|
}
|
|
276
|
-
|
|
277
|
-
if (
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
if (!params[token]) {
|
|
281
|
-
params[token] = new Set();
|
|
282
|
-
}
|
|
283
|
-
params[token].add(val);
|
|
284
|
-
}
|
|
285
|
-
i++;
|
|
814
|
+
const key = `${token}\u0000${value}`;
|
|
815
|
+
if (!seen.has(key)) {
|
|
816
|
+
seen.add(key);
|
|
817
|
+
merged.push(token, value);
|
|
286
818
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
if (token === "-Xfrontend") {
|
|
822
|
+
// -Xfrontend groups were validated while sanitizing
|
|
823
|
+
const group = [token, args[i + 1]];
|
|
824
|
+
i++;
|
|
825
|
+
if (
|
|
826
|
+
SWIFT_FRONTEND_ARGS_WITH_VALUE.has(group[1]) &&
|
|
827
|
+
args[i + 1] === "-Xfrontend"
|
|
828
|
+
) {
|
|
829
|
+
group.push(args[i + 1], args[i + 2]);
|
|
830
|
+
i += 2;
|
|
831
|
+
}
|
|
832
|
+
const key = group.join("\u0000");
|
|
833
|
+
if (!seen.has(key)) {
|
|
834
|
+
seen.add(key);
|
|
835
|
+
merged.push(...group);
|
|
296
836
|
}
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
if (!seen.has(token)) {
|
|
840
|
+
seen.add(token);
|
|
841
|
+
merged.push(token);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
for (const group of groupClangArgs(xccValues)) {
|
|
845
|
+
const key = group.join("\u0000");
|
|
846
|
+
if (!seenClangGroups.has(key)) {
|
|
847
|
+
seenClangGroups.add(key);
|
|
848
|
+
clangGroups.push(group);
|
|
297
849
|
}
|
|
298
850
|
}
|
|
299
851
|
}
|
|
300
|
-
const
|
|
852
|
+
for (const group of clangGroups) {
|
|
853
|
+
for (const value of group) {
|
|
854
|
+
merged.push("-Xcc", value);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
return merged;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* Method to parse the verbose swift build output to identify key compiler
|
|
862
|
+
* parameters. The arguments of the root package's modules are merged, or of
|
|
863
|
+
* every module when the root modules are unknown.
|
|
864
|
+
*
|
|
865
|
+
* @param {String} buildOutput Verbose build output
|
|
866
|
+
* @param {Array<String>|Set<String>} [rootModules] Module names of the root package
|
|
867
|
+
* @returns {Object} `{ params, compilerArgs }` where params maps module names to their arguments
|
|
868
|
+
*/
|
|
869
|
+
export function extractCompilerParamsFromBuild(buildOutput, rootModules) {
|
|
870
|
+
if (!buildOutput) {
|
|
871
|
+
console.log("No build output. Returning empty compilerArgs");
|
|
872
|
+
return { params: {}, compilerArgs: [] };
|
|
873
|
+
}
|
|
874
|
+
const invocations = parseSwiftModuleInvocations(buildOutput);
|
|
875
|
+
const rootSet = new Set(rootModules || []);
|
|
876
|
+
let selected = Object.keys(invocations).filter((m) => rootSet.has(m));
|
|
877
|
+
if (!selected.length) {
|
|
878
|
+
selected = Object.keys(invocations);
|
|
879
|
+
}
|
|
880
|
+
selected.sort(
|
|
881
|
+
(a, b) => invocations[b].args.length - invocations[a].args.length,
|
|
882
|
+
);
|
|
883
|
+
const compilerArgs = [];
|
|
301
884
|
if (process?.env?.SWIFT_COMPILER_EXTRA_ARGS) {
|
|
302
|
-
|
|
885
|
+
compilerArgs.push(
|
|
886
|
+
...splitSwiftCommandArgs(process.env.SWIFT_COMPILER_EXTRA_ARGS),
|
|
887
|
+
);
|
|
303
888
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}
|
|
311
|
-
} else if (Array.isArray(value)) {
|
|
312
|
-
for (const v of value) {
|
|
313
|
-
compilerArgsList.push(key);
|
|
314
|
-
compilerArgsList.push(v);
|
|
315
|
-
}
|
|
316
|
-
} else if (typeof value === "string") {
|
|
317
|
-
compilerArgsList.push(key);
|
|
318
|
-
compilerArgsList.push(value);
|
|
319
|
-
} else if (value === true) {
|
|
320
|
-
compilerArgsList.push(key);
|
|
321
|
-
}
|
|
889
|
+
compilerArgs.push(
|
|
890
|
+
...mergeSwiftModuleArgs(selected.map((m) => invocations[m].args)),
|
|
891
|
+
);
|
|
892
|
+
const params = {};
|
|
893
|
+
for (const m of selected) {
|
|
894
|
+
params[m] = invocations[m].args;
|
|
322
895
|
}
|
|
323
|
-
return { params, compilerArgs
|
|
896
|
+
return { params, compilerArgs };
|
|
324
897
|
}
|
|
325
898
|
|
|
326
899
|
/**
|
|
327
900
|
* Method to index a swift file and extract metadata
|
|
328
901
|
*
|
|
329
902
|
* @param {String} filePath Path to .swift file
|
|
330
|
-
* @param {String|Array<string>} compilerArgs Compiler arguments
|
|
903
|
+
* @param {String|Array<string>} compilerArgs Compiler arguments. They should
|
|
904
|
+
* list every source of the file's module so that references across files
|
|
905
|
+
* resolve; the file itself is appended when missing.
|
|
906
|
+
* @param {Object} [env] Environment overrides for sourcekitten
|
|
331
907
|
* @returns {undefined|Object} metadata
|
|
332
908
|
*/
|
|
333
|
-
export function index(filePath, compilerArgs) {
|
|
909
|
+
export function index(filePath, compilerArgs, env) {
|
|
334
910
|
const skArgs = ["index", "--file", filePath];
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
911
|
+
const args = Array.isArray(compilerArgs)
|
|
912
|
+
? compilerArgs
|
|
913
|
+
: compilerArgs
|
|
914
|
+
? splitSwiftCommandArgs(compilerArgs)
|
|
915
|
+
: [];
|
|
916
|
+
skArgs.push("--", ...args);
|
|
917
|
+
if (!args.includes(filePath)) {
|
|
918
|
+
skArgs.push(filePath);
|
|
342
919
|
}
|
|
343
|
-
skArgs
|
|
344
|
-
return executeSourcekitten(skArgs);
|
|
920
|
+
return executeSourcekitten(skArgs, env);
|
|
345
921
|
}
|
|
346
922
|
|
|
347
923
|
/**
|
|
348
924
|
* Parse the data from the index command
|
|
349
925
|
*
|
|
926
|
+
* Besides the symbol names and lines, the unified symbol resolution (USR) of
|
|
927
|
+
* every indexed entity is recorded with its lines. A USR identifies the
|
|
928
|
+
* declaration the compiler resolved a reference to, including the module
|
|
929
|
+
* that declares it, which is what attributes a usage to a dependency.
|
|
930
|
+
*
|
|
350
931
|
* @param {Object} indexJson Json from the index command
|
|
351
932
|
* @returns {Object|undefined} Parsed value
|
|
352
933
|
*/
|
|
@@ -366,16 +947,20 @@ export function parseIndex(indexJson) {
|
|
|
366
947
|
const obfuscatedSymbols = {};
|
|
367
948
|
// Line numbers where the given symbols are found
|
|
368
949
|
const symbolLocations = {};
|
|
950
|
+
// Line numbers of every resolved entity, keyed by its USR
|
|
951
|
+
const usrLines = {};
|
|
369
952
|
buildIndexedObfuscatedSymbols(
|
|
370
953
|
indexJson["key.entities"],
|
|
371
954
|
obfuscatedSymbols,
|
|
372
955
|
symbolLocations,
|
|
956
|
+
usrLines,
|
|
373
957
|
);
|
|
374
958
|
return {
|
|
375
959
|
swiftModules: Array.from(swiftModules).sort(),
|
|
376
960
|
clangModules: Array.from(clangModules).sort(),
|
|
377
961
|
obfuscatedSymbols,
|
|
378
962
|
symbolLocations,
|
|
963
|
+
usrLines,
|
|
379
964
|
};
|
|
380
965
|
}
|
|
381
966
|
|
|
@@ -387,7 +972,7 @@ export function parseIndex(indexJson) {
|
|
|
387
972
|
* @param clangModules {Set<String>} clang modules
|
|
388
973
|
*/
|
|
389
974
|
function collectIndexedModules(dependencies, swiftModules, clangModules) {
|
|
390
|
-
for (const adep of dependencies) {
|
|
975
|
+
for (const adep of dependencies || []) {
|
|
391
976
|
if (adep["key.kind"] === SWIFT_ENTITY_KINDS.IMPORT_SWIFT) {
|
|
392
977
|
swiftModules.add(adep["key.name"]);
|
|
393
978
|
} else if (adep["key.kind"] === SWIFT_ENTITY_KINDS.IMPORT_CLANG) {
|
|
@@ -409,11 +994,13 @@ function collectIndexedModules(dependencies, swiftModules, clangModules) {
|
|
|
409
994
|
* @param entities {Array} Entities found in the index data
|
|
410
995
|
* @param obfuscatedSymbols {Object} Obfuscated symbols map
|
|
411
996
|
* @param symbolLocations {Object} Symbol locations
|
|
997
|
+
* @param usrLines {Object} Lines of each USR
|
|
412
998
|
*/
|
|
413
999
|
function buildIndexedObfuscatedSymbols(
|
|
414
1000
|
entities,
|
|
415
1001
|
obfuscatedSymbols,
|
|
416
1002
|
symbolLocations,
|
|
1003
|
+
usrLines,
|
|
417
1004
|
) {
|
|
418
1005
|
if (!entities) {
|
|
419
1006
|
return;
|
|
@@ -423,18 +1010,27 @@ function buildIndexedObfuscatedSymbols(
|
|
|
423
1010
|
obfuscatedSymbols[aentity["key.name"]] = aentity["key.usr"];
|
|
424
1011
|
}
|
|
425
1012
|
if (aentity["key.line"]) {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
) {
|
|
434
|
-
symbolLocations[symbolLocationsKey] = [];
|
|
1013
|
+
if (aentity["key.usr"]) {
|
|
1014
|
+
if (!usrLines[aentity["key.usr"]]) {
|
|
1015
|
+
usrLines[aentity["key.usr"]] = [];
|
|
1016
|
+
}
|
|
1017
|
+
if (!usrLines[aentity["key.usr"]].includes(aentity["key.line"])) {
|
|
1018
|
+
usrLines[aentity["key.usr"]].push(aentity["key.line"]);
|
|
1019
|
+
}
|
|
435
1020
|
}
|
|
436
|
-
|
|
437
|
-
|
|
1021
|
+
const symbolLocationsKey = aentity["key.name"] || aentity["key.usr"];
|
|
1022
|
+
if (symbolLocationsKey) {
|
|
1023
|
+
if (
|
|
1024
|
+
!symbolLocations[symbolLocationsKey] ||
|
|
1025
|
+
!Array.isArray(symbolLocations[symbolLocationsKey])
|
|
1026
|
+
) {
|
|
1027
|
+
symbolLocations[symbolLocationsKey] = [];
|
|
1028
|
+
}
|
|
1029
|
+
if (
|
|
1030
|
+
!symbolLocations[symbolLocationsKey].includes(aentity["key.line"])
|
|
1031
|
+
) {
|
|
1032
|
+
symbolLocations[symbolLocationsKey].push(aentity["key.line"]);
|
|
1033
|
+
}
|
|
438
1034
|
}
|
|
439
1035
|
}
|
|
440
1036
|
if (aentity["key.entities"]) {
|
|
@@ -442,33 +1038,204 @@ function buildIndexedObfuscatedSymbols(
|
|
|
442
1038
|
aentity["key.entities"],
|
|
443
1039
|
obfuscatedSymbols,
|
|
444
1040
|
symbolLocations,
|
|
1041
|
+
usrLines,
|
|
445
1042
|
);
|
|
446
1043
|
}
|
|
447
1044
|
}
|
|
448
1045
|
}
|
|
449
1046
|
|
|
1047
|
+
// Declaration qualifiers the demangler prints ahead of the qualified name
|
|
1048
|
+
const DEMANGLED_QUALIFIERS = new Set([
|
|
1049
|
+
"static",
|
|
1050
|
+
"class",
|
|
1051
|
+
"mutating",
|
|
1052
|
+
"nonmutating",
|
|
1053
|
+
"__owned",
|
|
1054
|
+
"__shared",
|
|
1055
|
+
"__consuming",
|
|
1056
|
+
"borrowing",
|
|
1057
|
+
"consuming",
|
|
1058
|
+
"dynamic",
|
|
1059
|
+
"distributed",
|
|
1060
|
+
"nonisolated",
|
|
1061
|
+
"isolated",
|
|
1062
|
+
]);
|
|
1063
|
+
|
|
1064
|
+
function isIdentifierChar(char) {
|
|
1065
|
+
return (
|
|
1066
|
+
(char >= "a" && char <= "z") ||
|
|
1067
|
+
(char >= "A" && char <= "Z") ||
|
|
1068
|
+
(char >= "0" && char <= "9") ||
|
|
1069
|
+
char === "_"
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
|
|
450
1073
|
/**
|
|
451
|
-
*
|
|
1074
|
+
* Identify the module that declares an entity from its demangled name.
|
|
452
1075
|
*
|
|
453
|
-
*
|
|
454
|
-
*
|
|
1076
|
+
* Qualified names start with the module (`Yams.dump(object:...)`), and
|
|
1077
|
+
* members declared in extensions carry the extending module
|
|
1078
|
+
* (`(extension in Yams):Swift.String.yaml`), which is what a usage of the
|
|
1079
|
+
* member depends on.
|
|
1080
|
+
*
|
|
1081
|
+
* @param {String} demangled Output of `swift demangle --compact`
|
|
1082
|
+
* @returns {undefined|String} Module name
|
|
455
1083
|
*/
|
|
456
|
-
export function
|
|
457
|
-
|
|
458
|
-
if (!
|
|
1084
|
+
export function moduleFromDemangledName(demangled) {
|
|
1085
|
+
let text = (demangled || "").trim();
|
|
1086
|
+
if (!text || text.startsWith("$")) {
|
|
459
1087
|
return undefined;
|
|
460
1088
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
1089
|
+
for (let round = 0; round < 4; round++) {
|
|
1090
|
+
const space = text.indexOf(" ");
|
|
1091
|
+
if (space > 0 && DEMANGLED_QUALIFIERS.has(text.slice(0, space))) {
|
|
1092
|
+
text = text.slice(space + 1);
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
// Descriptions such as `default argument 0 of X` or
|
|
1096
|
+
// `variable initialization expression of X`
|
|
1097
|
+
const of = text.indexOf(" of ");
|
|
1098
|
+
const prefix = of > 0 ? text.slice(0, of) : "";
|
|
1099
|
+
if (prefix && !prefix.includes(".") && !prefix.includes("(")) {
|
|
1100
|
+
text = text.slice(of + 4);
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1103
|
+
break;
|
|
465
1104
|
}
|
|
1105
|
+
const extensionPrefix = "(extension in ";
|
|
1106
|
+
if (text.startsWith(extensionPrefix)) {
|
|
1107
|
+
const end = text.indexOf("):");
|
|
1108
|
+
return end > extensionPrefix.length
|
|
1109
|
+
? text.slice(extensionPrefix.length, end)
|
|
1110
|
+
: undefined;
|
|
1111
|
+
}
|
|
1112
|
+
let i = 0;
|
|
1113
|
+
while (i < text.length && isIdentifierChar(text[i])) {
|
|
1114
|
+
i++;
|
|
1115
|
+
}
|
|
1116
|
+
return i > 0 && text[i] === "." ? text.slice(0, i) : undefined;
|
|
466
1117
|
}
|
|
467
1118
|
|
|
468
1119
|
/**
|
|
469
|
-
*
|
|
1120
|
+
* Identify the module of a mangled Swift name without the demangler. Only the
|
|
1121
|
+
* leading module context is decoded, so extension members are attributed to
|
|
1122
|
+
* the extended type's module.
|
|
470
1123
|
*
|
|
471
|
-
* @param {
|
|
1124
|
+
* @param {String} mangled Mangled name starting with `$s`
|
|
1125
|
+
* @returns {undefined|String} Module name
|
|
1126
|
+
*/
|
|
1127
|
+
export function moduleFromMangledPrefix(mangled) {
|
|
1128
|
+
if (!mangled?.startsWith("$s")) {
|
|
1129
|
+
return undefined;
|
|
1130
|
+
}
|
|
1131
|
+
const rest = mangled.slice(2);
|
|
1132
|
+
if (rest.startsWith("s") || /^S[A-Za-z]/.test(rest)) {
|
|
1133
|
+
return rest.startsWith("So") || rest.startsWith("SC") ? "__C" : "Swift";
|
|
1134
|
+
}
|
|
1135
|
+
let i = 0;
|
|
1136
|
+
while (i < rest.length && rest[i] >= "0" && rest[i] <= "9") {
|
|
1137
|
+
i++;
|
|
1138
|
+
}
|
|
1139
|
+
// A leading 0 marks an identifier with word substitutions
|
|
1140
|
+
if (!i || rest[0] === "0") {
|
|
1141
|
+
return undefined;
|
|
1142
|
+
}
|
|
1143
|
+
const length = Number.parseInt(rest.slice(0, i), 10);
|
|
1144
|
+
const identifier = rest.slice(i, i + length);
|
|
1145
|
+
return identifier.length === length ? identifier : undefined;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* Convert a Swift USR (`s:<mangled>`, or `s:e:s:<mangled>` for extensions)
|
|
1150
|
+
* into a mangled name the demangler accepts.
|
|
1151
|
+
*
|
|
1152
|
+
* @param {String} usr USR
|
|
1153
|
+
* @returns {undefined|String} Mangled name starting with `$s`
|
|
1154
|
+
*/
|
|
1155
|
+
function swiftUsrToMangledName(usr) {
|
|
1156
|
+
let value = usr || "";
|
|
1157
|
+
if (value.startsWith("s:e:")) {
|
|
1158
|
+
value = value.slice(4);
|
|
1159
|
+
}
|
|
1160
|
+
return value.startsWith("s:") && value.length > 2
|
|
1161
|
+
? `$s${value.slice(2)}`
|
|
1162
|
+
: undefined;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
/**
|
|
1166
|
+
* Identify the declaring module of each USR.
|
|
1167
|
+
*
|
|
1168
|
+
* Swift USRs are demangled in one batch with `swift demangle`; clang module
|
|
1169
|
+
* references (`c:@M@<module>`, emitted for import declarations) carry the
|
|
1170
|
+
* module name directly. Other clang USRs name no module and are skipped.
|
|
1171
|
+
*
|
|
1172
|
+
* @param {Array<String>} usrs USRs to resolve
|
|
1173
|
+
* @param {String} basePath Directory to run the demangler in
|
|
1174
|
+
* @returns {Map<String, String>} USR to module name
|
|
1175
|
+
*/
|
|
1176
|
+
export function resolveUsrModules(usrs, basePath) {
|
|
1177
|
+
const usrModules = new Map();
|
|
1178
|
+
const pending = [];
|
|
1179
|
+
for (const usr of new Set(usrs)) {
|
|
1180
|
+
if (usr.startsWith("c:@M@")) {
|
|
1181
|
+
const moduleName = usr.slice(5).split("@")[0];
|
|
1182
|
+
if (moduleName) {
|
|
1183
|
+
usrModules.set(usr, moduleName);
|
|
1184
|
+
}
|
|
1185
|
+
continue;
|
|
1186
|
+
}
|
|
1187
|
+
const mangled = swiftUsrToMangledName(usr);
|
|
1188
|
+
if (mangled) {
|
|
1189
|
+
pending.push([usr, mangled]);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
if (!pending.length) {
|
|
1193
|
+
return usrModules;
|
|
1194
|
+
}
|
|
1195
|
+
const result = spawnSwift(basePath, ["demangle", "--compact"], {
|
|
1196
|
+
input: `${pending.map(([, mangled]) => mangled).join("\n")}\n`,
|
|
1197
|
+
});
|
|
1198
|
+
const demangledLines =
|
|
1199
|
+
result?.status === 0 && result.stdout ? result.stdout.split("\n") : [];
|
|
1200
|
+
if (!demangledLines.length && DEBUG_MODE) {
|
|
1201
|
+
console.log(
|
|
1202
|
+
"swift demangle is unavailable. Falling back to the mangled module prefix.",
|
|
1203
|
+
);
|
|
1204
|
+
}
|
|
1205
|
+
pending.forEach(([usr, mangled], i) => {
|
|
1206
|
+
const moduleName =
|
|
1207
|
+
moduleFromDemangledName(demangledLines[i]) ||
|
|
1208
|
+
moduleFromMangledPrefix(mangled);
|
|
1209
|
+
if (moduleName) {
|
|
1210
|
+
usrModules.set(usr, moduleName);
|
|
1211
|
+
}
|
|
1212
|
+
});
|
|
1213
|
+
return usrModules;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
/**
|
|
1217
|
+
* Method to execute dump-package package command.
|
|
1218
|
+
*
|
|
1219
|
+
* @param {String} basePath Path
|
|
1220
|
+
* @returns {undefined|Object} Output from dump-package command
|
|
1221
|
+
*/
|
|
1222
|
+
export function dumpPackage(basePath) {
|
|
1223
|
+
// stderr carries manifest warnings, so only stdout is parsed
|
|
1224
|
+
const result = spawnSwift(basePath, ["package", "dump-package"]);
|
|
1225
|
+
if (!result?.stdout) {
|
|
1226
|
+
return undefined;
|
|
1227
|
+
}
|
|
1228
|
+
try {
|
|
1229
|
+
return JSON.parse(result.stdout);
|
|
1230
|
+
} catch (_e) {
|
|
1231
|
+
return undefined;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
/**
|
|
1236
|
+
* Parse the data from dump-package command
|
|
1237
|
+
*
|
|
1238
|
+
* @param {Object} dumpJson Json from dump-package command
|
|
472
1239
|
* @returns {Object|undefined} Parsed value
|
|
473
1240
|
*/
|
|
474
1241
|
export function parseDumpPackage(dumpJson) {
|
|
@@ -481,12 +1248,29 @@ export function parseDumpPackage(dumpJson) {
|
|
|
481
1248
|
platforms: dumpJson?.platforms,
|
|
482
1249
|
};
|
|
483
1250
|
const rootPkgDependencies = [];
|
|
1251
|
+
const rootModules = new Set();
|
|
1252
|
+
// Test targets and the library targets only tests depend on
|
|
1253
|
+
const testModules = new Set();
|
|
1254
|
+
const productionTargets = collectSwiftProductionTargets(dumpJson);
|
|
484
1255
|
if (dumpJson.targets) {
|
|
485
1256
|
for (const atarget of dumpJson.targets) {
|
|
486
|
-
const ref = atarget.name.
|
|
1257
|
+
const ref = atarget.name.replaceAll("+", "_");
|
|
1258
|
+
if (productionTargets.has(String(atarget.name).toLowerCase())) {
|
|
1259
|
+
rootModules.add(toModuleName(ref));
|
|
1260
|
+
} else {
|
|
1261
|
+
testModules.add(toModuleName(ref));
|
|
1262
|
+
}
|
|
487
1263
|
if (atarget.dependencies) {
|
|
1264
|
+
// Product dependencies such as `.product(name:package:)` record both
|
|
1265
|
+
// the product (module) name and the package identity; `byName` and
|
|
1266
|
+
// `target` entries only record the product or target name.
|
|
488
1267
|
const dependsOn = atarget.dependencies
|
|
489
|
-
.map(
|
|
1268
|
+
.map(
|
|
1269
|
+
(v) =>
|
|
1270
|
+
v?.byName?.[0]?.replaceAll("+", "_") ??
|
|
1271
|
+
v?.target?.[0]?.replaceAll("+", "_") ??
|
|
1272
|
+
v?.product?.[0]?.replaceAll("+", "_"),
|
|
1273
|
+
)
|
|
490
1274
|
.filter((v) => v !== undefined);
|
|
491
1275
|
rootPkgDependencies.push({
|
|
492
1276
|
ref,
|
|
@@ -496,6 +1280,12 @@ export function parseDumpPackage(dumpJson) {
|
|
|
496
1280
|
}
|
|
497
1281
|
}
|
|
498
1282
|
metadata.dependencies = rootPkgDependencies;
|
|
1283
|
+
if (rootModules.size) {
|
|
1284
|
+
metadata.rootModules = Array.from(rootModules).sort();
|
|
1285
|
+
}
|
|
1286
|
+
if (testModules.size) {
|
|
1287
|
+
metadata.testModules = Array.from(testModules).sort();
|
|
1288
|
+
}
|
|
499
1289
|
return metadata;
|
|
500
1290
|
}
|
|
501
1291
|
|
|
@@ -504,19 +1294,20 @@ export function parseDumpPackage(dumpJson) {
|
|
|
504
1294
|
*
|
|
505
1295
|
* @param {String} moduleName Module name
|
|
506
1296
|
* @param {String|Array<string>} compilerArgs Compiler arguments extracted from verbose build log
|
|
1297
|
+
* @param {Object} [env] Environment overrides for sourcekitten
|
|
507
1298
|
* @returns {undefined|Object} JSON representation of the swift module or undefined.
|
|
508
1299
|
*/
|
|
509
|
-
export function moduleInfo(moduleName, compilerArgs) {
|
|
1300
|
+
export function moduleInfo(moduleName, compilerArgs, env) {
|
|
510
1301
|
const skArgs = ["module-info", "--module", moduleName];
|
|
511
1302
|
if (compilerArgs && compilerArgs.length > 0) {
|
|
512
1303
|
skArgs.push("--");
|
|
513
1304
|
if (Array.isArray(compilerArgs)) {
|
|
514
1305
|
skArgs.push(...compilerArgs);
|
|
515
1306
|
} else {
|
|
516
|
-
skArgs.push(...compilerArgs
|
|
1307
|
+
skArgs.push(...splitSwiftCommandArgs(compilerArgs));
|
|
517
1308
|
}
|
|
518
1309
|
}
|
|
519
|
-
return executeSourcekitten(skArgs);
|
|
1310
|
+
return executeSourcekitten(skArgs, env);
|
|
520
1311
|
}
|
|
521
1312
|
|
|
522
1313
|
/**
|
|
@@ -593,12 +1384,24 @@ export function parseModuleInfo(moduleInfoJson) {
|
|
|
593
1384
|
}
|
|
594
1385
|
}
|
|
595
1386
|
}
|
|
596
|
-
// Collect the imported modules
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
.
|
|
1387
|
+
// Collect the imported modules and the free functions offered by the module
|
|
1388
|
+
const freeFunctions = new Set();
|
|
1389
|
+
const importedModules = [];
|
|
1390
|
+
for (const aline of (moduleInfoJson?.["key.sourcetext"] || "").split("\n")) {
|
|
1391
|
+
const line = aline.replaceAll("\r", "");
|
|
1392
|
+
if (line.startsWith("import ")) {
|
|
1393
|
+
importedModules.push(line.replace("import ", ""));
|
|
1394
|
+
continue;
|
|
1395
|
+
}
|
|
1396
|
+
// Free functions such as Yams' `dump(object:)` are declared at the top
|
|
1397
|
+
// level of the interface; members of types and extensions are indented
|
|
1398
|
+
const funcMatch = line.match(
|
|
1399
|
+
/^(?:@\w+(?:\([^)]*\))?\s+)*(?:public\s+|open\s+)?func\s+([A-Za-z_][A-Za-z0-9_]*)/,
|
|
1400
|
+
);
|
|
1401
|
+
if (funcMatch) {
|
|
1402
|
+
freeFunctions.add(funcMatch[1]);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
602
1405
|
return {
|
|
603
1406
|
classes: Array.from(classes).sort(),
|
|
604
1407
|
protocols: Array.from(protocols).sort(),
|
|
@@ -606,162 +1409,971 @@ export function parseModuleInfo(moduleInfoJson) {
|
|
|
606
1409
|
obfuscationMap,
|
|
607
1410
|
classMethods,
|
|
608
1411
|
protocolMethods,
|
|
609
|
-
|
|
1412
|
+
functions: Array.from(freeFunctions).sort(),
|
|
1413
|
+
importedModules: importedModules.sort(),
|
|
610
1414
|
};
|
|
611
1415
|
}
|
|
612
1416
|
|
|
613
1417
|
/**
|
|
614
|
-
* Method to collect the build symbols from the output file maps generated by
|
|
1418
|
+
* Method to collect the build symbols from the output file maps generated by
|
|
1419
|
+
* swift build.
|
|
1420
|
+
*
|
|
1421
|
+
* Both build engines are supported: the llbuild engine writes per-module
|
|
1422
|
+
* `output-file-map.json` files, while the Swift Build engine (default from
|
|
1423
|
+
* Swift 6.4) writes per-target `<target>-OutputFileMap.json` files under
|
|
1424
|
+
* `.build/out/Intermediates.noindex`.
|
|
615
1425
|
*
|
|
616
1426
|
* @param {String} basePath Path
|
|
617
1427
|
* @param {Object} options CLI options
|
|
618
|
-
* @
|
|
1428
|
+
* @param {Array<String>} [fileMaps] Output file maps, found under basePath when omitted
|
|
1429
|
+
* @returns {Object} symbols map keyed by module name
|
|
619
1430
|
*/
|
|
620
|
-
export function collectBuildSymbols(basePath, options) {
|
|
621
|
-
const outputFileMaps = getAllFiles(
|
|
622
|
-
basePath,
|
|
623
|
-
".build/**/debug/**/output-file-map.json",
|
|
624
|
-
options,
|
|
625
|
-
);
|
|
1431
|
+
export function collectBuildSymbols(basePath, options, fileMaps) {
|
|
626
1432
|
const symbolsMap = {};
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
1433
|
+
const mapFiles =
|
|
1434
|
+
fileMaps ||
|
|
1435
|
+
getAllFiles(
|
|
1436
|
+
basePath,
|
|
1437
|
+
`.build/**/{output-file-map.json,*${SWIFT_BUILD_FILEMAP_SUFFIX}}`,
|
|
1438
|
+
options,
|
|
1439
|
+
);
|
|
1440
|
+
for (const afilemap of mapFiles) {
|
|
1441
|
+
let metadata;
|
|
1442
|
+
try {
|
|
1443
|
+
metadata = parseOutputFileMap(afilemap);
|
|
1444
|
+
} catch (_e) {
|
|
1445
|
+
continue;
|
|
1446
|
+
}
|
|
1447
|
+
if (!metadata?.moduleName || isTestOrAggregateModule(metadata.moduleName)) {
|
|
631
1448
|
continue;
|
|
632
1449
|
}
|
|
633
|
-
symbolsMap[metadata.moduleName]
|
|
1450
|
+
const existing = symbolsMap[metadata.moduleName] || [];
|
|
1451
|
+
for (const asym of metadata.moduleSymbols) {
|
|
1452
|
+
if (!existing.includes(asym)) {
|
|
1453
|
+
existing.push(asym);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
symbolsMap[metadata.moduleName] = existing;
|
|
634
1457
|
}
|
|
635
1458
|
return symbolsMap;
|
|
636
1459
|
}
|
|
637
1460
|
|
|
638
1461
|
/**
|
|
639
|
-
* Method to parse output file map to identify the module
|
|
640
|
-
*
|
|
1462
|
+
* Method to parse output file map to identify the module, its sources, and
|
|
1463
|
+
* their symbols. The symbol list is imprecise; the sources are exact.
|
|
1464
|
+
*
|
|
1465
|
+
* The module name is derived from the containing directory for llbuild maps
|
|
1466
|
+
* (`<Module>.build` and, since Swift 6.4, `<Module>-tool.build`) and from the
|
|
1467
|
+
* file name for Swift Build maps (`<target>-OutputFileMap.json`, where the
|
|
1468
|
+
* target name still needs to be sanitised into a module name).
|
|
641
1469
|
*
|
|
642
1470
|
* @param filemap {String} File name
|
|
643
|
-
* @returns {Object} parsed module metadata
|
|
1471
|
+
* @returns {Object} parsed module metadata `{ moduleName, moduleSymbols, sourceFiles }`
|
|
644
1472
|
*/
|
|
645
1473
|
export function parseOutputFileMap(filemap) {
|
|
646
|
-
const moduleName = basename(dirname(filemap)).replace(".build", "");
|
|
647
1474
|
const fileMapObj = JSON.parse(readFileSync(filemap, { encoding: "utf-8" }));
|
|
648
|
-
|
|
1475
|
+
const fileMapName = basename(filemap);
|
|
1476
|
+
let moduleName;
|
|
1477
|
+
if (fileMapName.endsWith(SWIFT_BUILD_FILEMAP_SUFFIX)) {
|
|
1478
|
+
moduleName = toModuleName(
|
|
1479
|
+
fileMapName.slice(0, -SWIFT_BUILD_FILEMAP_SUFFIX.length),
|
|
1480
|
+
);
|
|
1481
|
+
} else {
|
|
1482
|
+
moduleName = basename(dirname(filemap))
|
|
1483
|
+
.replace(/\.build$/, "")
|
|
1484
|
+
.replace(/-tool$/, "");
|
|
1485
|
+
}
|
|
649
1486
|
const moduleSymbols = [];
|
|
1487
|
+
const sourceFiles = [];
|
|
650
1488
|
for (const akey of Object.keys(fileMapObj)) {
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
1489
|
+
// The empty key describes module-level outputs rather than a source file
|
|
1490
|
+
if (!akey.length) {
|
|
1491
|
+
continue;
|
|
1492
|
+
}
|
|
1493
|
+
if (akey.endsWith(".swift")) {
|
|
1494
|
+
sourceFiles.push(akey);
|
|
1495
|
+
}
|
|
1496
|
+
const swiftDeps = fileMapObj[akey]?.["swift-dependencies"];
|
|
1497
|
+
if (swiftDeps) {
|
|
1498
|
+
moduleSymbols.push(
|
|
1499
|
+
basename(swiftDeps).replace(".swiftdeps", "").replaceAll("+", "_"),
|
|
1500
|
+
);
|
|
656
1501
|
}
|
|
657
1502
|
}
|
|
658
|
-
return { moduleName, moduleSymbols };
|
|
1503
|
+
return { moduleName, moduleSymbols, sourceFiles };
|
|
659
1504
|
}
|
|
660
1505
|
|
|
661
1506
|
/**
|
|
662
|
-
*
|
|
1507
|
+
* Parse `.build/workspace-state.json` into the dependency packages of the
|
|
1508
|
+
* workspace and the directory each one was materialised in.
|
|
663
1509
|
*
|
|
664
|
-
* @param
|
|
665
|
-
* @param
|
|
1510
|
+
* @param {Object} stateJson Parsed workspace-state.json
|
|
1511
|
+
* @param {String} basePath Root package directory
|
|
1512
|
+
* @returns {Array<Object>} `{ identity, name, location, kind, dir }` per package
|
|
666
1513
|
*/
|
|
667
|
-
export function
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
1514
|
+
export function parseSwiftWorkspaceState(stateJson, basePath) {
|
|
1515
|
+
const packages = [];
|
|
1516
|
+
const dependencies =
|
|
1517
|
+
stateJson?.object?.dependencies || stateJson?.dependencies || [];
|
|
1518
|
+
for (const adep of dependencies) {
|
|
1519
|
+
const packageRef = adep?.packageRef || {};
|
|
1520
|
+
const identity = packageRef.identity;
|
|
1521
|
+
if (!identity) {
|
|
1522
|
+
continue;
|
|
1523
|
+
}
|
|
1524
|
+
let dir;
|
|
1525
|
+
switch (adep?.state?.name) {
|
|
1526
|
+
case "sourceControlCheckout":
|
|
1527
|
+
case "checkout":
|
|
1528
|
+
dir = join(basePath, ".build", "checkouts", adep.subpath || identity);
|
|
1529
|
+
break;
|
|
1530
|
+
case "registryDownload":
|
|
1531
|
+
dir = join(
|
|
1532
|
+
basePath,
|
|
1533
|
+
".build",
|
|
1534
|
+
"registry",
|
|
1535
|
+
"downloads",
|
|
1536
|
+
adep.subpath || identity,
|
|
1537
|
+
);
|
|
1538
|
+
break;
|
|
1539
|
+
case "fileSystem":
|
|
1540
|
+
case "local":
|
|
1541
|
+
dir = adep.state?.path || packageRef.location;
|
|
1542
|
+
break;
|
|
1543
|
+
case "edited":
|
|
1544
|
+
dir = adep.state?.path || join(basePath, "Packages", adep.subpath);
|
|
1545
|
+
break;
|
|
1546
|
+
default:
|
|
1547
|
+
dir = undefined;
|
|
1548
|
+
}
|
|
1549
|
+
packages.push({
|
|
1550
|
+
identity,
|
|
1551
|
+
name: packageRef.name || identity,
|
|
1552
|
+
location: packageRef.location,
|
|
1553
|
+
kind: packageRef.kind,
|
|
1554
|
+
dir: dir && isAbsolute(dir) ? dir : undefined,
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
return packages;
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
/**
|
|
1561
|
+
* Parse the Swift Build engine's project model (`.build/manifest.pif`) into
|
|
1562
|
+
* the module names each package declares.
|
|
1563
|
+
*
|
|
1564
|
+
* The manifest is a list of workspace, project, and target objects linked
|
|
1565
|
+
* through their `signature` hashes. A project's guid is `PACKAGE:<identity>`
|
|
1566
|
+
* and each target records its `PRODUCT_MODULE_NAME`. Unlike the verbose
|
|
1567
|
+
* build output, the manifest lists modules whose compilation never started,
|
|
1568
|
+
* which keeps the module-to-package mapping complete when a build fails.
|
|
1569
|
+
*
|
|
1570
|
+
* @param {Array} manifestJson Parsed manifest.pif contents
|
|
1571
|
+
* @returns {Object} Map of package identity to module names
|
|
1572
|
+
*/
|
|
1573
|
+
export function parseSwiftBuildManifest(manifestJson) {
|
|
1574
|
+
const packageModules = {};
|
|
1575
|
+
if (!Array.isArray(manifestJson)) {
|
|
1576
|
+
return packageModules;
|
|
1577
|
+
}
|
|
1578
|
+
const objectsBySignature = new Map();
|
|
1579
|
+
for (const obj of manifestJson) {
|
|
1580
|
+
if (obj?.signature) {
|
|
1581
|
+
objectsBySignature.set(obj.signature, obj);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
for (const obj of manifestJson) {
|
|
1585
|
+
const guid = obj?.type === "project" ? obj.contents?.guid : undefined;
|
|
1586
|
+
if (!guid?.startsWith("PACKAGE:")) {
|
|
1587
|
+
continue;
|
|
1588
|
+
}
|
|
1589
|
+
const identity = guid.slice("PACKAGE:".length);
|
|
1590
|
+
const modules = new Set();
|
|
1591
|
+
for (const targetSignature of obj.contents.targets || []) {
|
|
1592
|
+
const target = objectsBySignature.get(targetSignature);
|
|
1593
|
+
const contents = target?.type === "target" ? target.contents : undefined;
|
|
1594
|
+
if (!contents?.name || isTestOrAggregateModule(contents.name)) {
|
|
1595
|
+
continue;
|
|
1596
|
+
}
|
|
1597
|
+
for (const buildConfig of contents.buildConfigurations || []) {
|
|
1598
|
+
const moduleName = buildConfig?.buildSettings?.PRODUCT_MODULE_NAME;
|
|
1599
|
+
if (moduleName && !isTestOrAggregateModule(moduleName)) {
|
|
1600
|
+
modules.add(moduleName);
|
|
1601
|
+
break;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
if (modules.size) {
|
|
1606
|
+
packageModules[identity] = Array.from(modules).sort();
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
return packageModules;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
/**
|
|
1613
|
+
* Blank out the comments and string literals of Swift source, keeping line
|
|
1614
|
+
* breaks so line numbers are unchanged.
|
|
1615
|
+
*
|
|
1616
|
+
* Handles line comments, nested block comments, and single-line, multi-line
|
|
1617
|
+
* (`"""`) and raw (`#"..."#`) string literals.
|
|
1618
|
+
*
|
|
1619
|
+
* @param {String} sourceText Swift source
|
|
1620
|
+
* @returns {String} Source with comments and string contents replaced by spaces
|
|
1621
|
+
*/
|
|
1622
|
+
function maskSwiftCommentsAndStrings(sourceText) {
|
|
1623
|
+
const text = sourceText || "";
|
|
1624
|
+
let out = "";
|
|
1625
|
+
let i = 0;
|
|
1626
|
+
const blank = (s) => s.replace(/[^\n]/g, " ");
|
|
1627
|
+
while (i < text.length) {
|
|
1628
|
+
const char = text[i];
|
|
1629
|
+
if (char === "/" && text[i + 1] === "/") {
|
|
1630
|
+
const end = text.indexOf("\n", i);
|
|
1631
|
+
const stop = end === -1 ? text.length : end;
|
|
1632
|
+
out += blank(text.slice(i, stop));
|
|
1633
|
+
i = stop;
|
|
1634
|
+
continue;
|
|
1635
|
+
}
|
|
1636
|
+
if (char === "/" && text[i + 1] === "*") {
|
|
1637
|
+
let depth = 0;
|
|
1638
|
+
let j = i;
|
|
1639
|
+
while (j < text.length) {
|
|
1640
|
+
if (text[j] === "/" && text[j + 1] === "*") {
|
|
1641
|
+
depth++;
|
|
1642
|
+
j += 2;
|
|
1643
|
+
} else if (text[j] === "*" && text[j + 1] === "/") {
|
|
1644
|
+
depth--;
|
|
1645
|
+
j += 2;
|
|
1646
|
+
if (!depth) {
|
|
1647
|
+
break;
|
|
1648
|
+
}
|
|
1649
|
+
} else {
|
|
1650
|
+
j++;
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
out += blank(text.slice(i, j));
|
|
1654
|
+
i = j;
|
|
1655
|
+
continue;
|
|
1656
|
+
}
|
|
1657
|
+
if (char === '"' || (char === "#" && /^#+"/.test(text.slice(i, i + 64)))) {
|
|
1658
|
+
let hashes = 0;
|
|
1659
|
+
while (text[i + hashes] === "#") {
|
|
1660
|
+
hashes++;
|
|
1661
|
+
}
|
|
1662
|
+
const quoteStart = i + hashes;
|
|
1663
|
+
const multiline = text.startsWith('"""', quoteStart);
|
|
1664
|
+
const closing = `${multiline ? '"""' : '"'}${"#".repeat(hashes)}`;
|
|
1665
|
+
let j = quoteStart + (multiline ? 3 : 1);
|
|
1666
|
+
while (j < text.length) {
|
|
1667
|
+
// Escapes need as many `#` as the delimiter in raw strings
|
|
1668
|
+
if (
|
|
1669
|
+
text[j] === "\\" &&
|
|
1670
|
+
text.startsWith("#".repeat(hashes), j + 1) &&
|
|
1671
|
+
j + 1 + hashes < text.length
|
|
1672
|
+
) {
|
|
1673
|
+
j += 2 + hashes;
|
|
1674
|
+
continue;
|
|
1675
|
+
}
|
|
1676
|
+
if (text.startsWith(closing, j)) {
|
|
1677
|
+
j += closing.length;
|
|
1678
|
+
break;
|
|
1679
|
+
}
|
|
1680
|
+
// An unterminated single-line literal ends at the line break
|
|
1681
|
+
if (!multiline && text[j] === "\n") {
|
|
1682
|
+
break;
|
|
1683
|
+
}
|
|
1684
|
+
j++;
|
|
1685
|
+
}
|
|
1686
|
+
out += blank(text.slice(i, j));
|
|
1687
|
+
i = j;
|
|
1688
|
+
continue;
|
|
1689
|
+
}
|
|
1690
|
+
out += char;
|
|
1691
|
+
i++;
|
|
1692
|
+
}
|
|
1693
|
+
return out;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
/**
|
|
1697
|
+
* Collect the modules imported by a Swift source file, with their lines.
|
|
1698
|
+
*
|
|
1699
|
+
* Declarations such as `@testable import Foo`, `public import Foo`,
|
|
1700
|
+
* `@_implementationOnly import cmark_gfm`, and `import struct Foo.Bar` are
|
|
1701
|
+
* recognised; comments, string literals, and `canImport` conditions are not
|
|
1702
|
+
* imports.
|
|
1703
|
+
*
|
|
1704
|
+
* @param {String} sourceText Swift source
|
|
1705
|
+
* @returns {Array<Object>} `{ module, line }` per import declaration
|
|
1706
|
+
*/
|
|
1707
|
+
export function parseSwiftImports(sourceText) {
|
|
1708
|
+
const imports = [];
|
|
1709
|
+
const accessLevels = new Set([
|
|
1710
|
+
"public",
|
|
1711
|
+
"package",
|
|
1712
|
+
"internal",
|
|
1713
|
+
"fileprivate",
|
|
1714
|
+
"private",
|
|
1715
|
+
"open",
|
|
1716
|
+
]);
|
|
1717
|
+
const importKinds = new Set([
|
|
1718
|
+
"struct",
|
|
1719
|
+
"class",
|
|
1720
|
+
"enum",
|
|
1721
|
+
"protocol",
|
|
1722
|
+
"typealias",
|
|
1723
|
+
"func",
|
|
1724
|
+
"var",
|
|
1725
|
+
"let",
|
|
1726
|
+
]);
|
|
1727
|
+
const lines = maskSwiftCommentsAndStrings(sourceText).split("\n");
|
|
1728
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1729
|
+
const line = lines[i].trim();
|
|
1730
|
+
if (!line.includes("import")) {
|
|
1731
|
+
continue;
|
|
1732
|
+
}
|
|
1733
|
+
for (const statement of line.split(";")) {
|
|
1734
|
+
const tokens = statement.trim().split(/\s+/);
|
|
1735
|
+
let t = 0;
|
|
1736
|
+
while (
|
|
1737
|
+
t < tokens.length &&
|
|
1738
|
+
(tokens[t].startsWith("@") || accessLevels.has(tokens[t]))
|
|
1739
|
+
) {
|
|
1740
|
+
t++;
|
|
1741
|
+
}
|
|
1742
|
+
if (tokens[t] !== "import") {
|
|
1743
|
+
continue;
|
|
1744
|
+
}
|
|
1745
|
+
t++;
|
|
1746
|
+
if (importKinds.has(tokens[t])) {
|
|
1747
|
+
t++;
|
|
1748
|
+
}
|
|
1749
|
+
const moduleName = tokens[t]?.split(".")[0];
|
|
1750
|
+
if (moduleName && isIdentifierChar(moduleName[0])) {
|
|
1751
|
+
imports.push({ module: moduleName, line: i + 1 });
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
return imports;
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
/**
|
|
1759
|
+
* Collect the clang module names declared at the top level of a module map.
|
|
1760
|
+
*
|
|
1761
|
+
* @param {String} moduleMapText module.modulemap contents
|
|
1762
|
+
* @returns {Object} `{ modules, headerPaths }`
|
|
1763
|
+
*/
|
|
1764
|
+
export function parseClangModuleMap(moduleMapText) {
|
|
1765
|
+
const modules = [];
|
|
1766
|
+
const headerPaths = [];
|
|
1767
|
+
let depth = 0;
|
|
1768
|
+
for (const rawLine of (moduleMapText || "").split("\n")) {
|
|
1769
|
+
const line = rawLine.split("//")[0].trim();
|
|
1770
|
+
if (!line) {
|
|
1771
|
+
continue;
|
|
1772
|
+
}
|
|
1773
|
+
const tokens = line.split(/\s+/);
|
|
1774
|
+
if (depth === 0) {
|
|
1775
|
+
let t = 0;
|
|
1776
|
+
while (["explicit", "framework", "extern"].includes(tokens[t])) {
|
|
1777
|
+
t++;
|
|
1778
|
+
}
|
|
1779
|
+
if (tokens[t] === "module" && tokens[t + 1]) {
|
|
1780
|
+
const moduleName = tokens[t + 1].split("{")[0];
|
|
1781
|
+
if (moduleName && moduleName !== "*") {
|
|
1782
|
+
modules.push(moduleName);
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
const quoteStart = line.indexOf('"');
|
|
1787
|
+
const quoteEnd = line.indexOf('"', quoteStart + 1);
|
|
1788
|
+
if (
|
|
1789
|
+
quoteStart >= 0 &&
|
|
1790
|
+
quoteEnd > quoteStart &&
|
|
1791
|
+
(tokens.includes("header") || tokens.includes("umbrella"))
|
|
1792
|
+
) {
|
|
1793
|
+
headerPaths.push(line.slice(quoteStart + 1, quoteEnd));
|
|
1794
|
+
}
|
|
1795
|
+
for (const char of line) {
|
|
1796
|
+
if (char === "{") {
|
|
1797
|
+
depth++;
|
|
1798
|
+
} else if (char === "}") {
|
|
1799
|
+
depth = Math.max(0, depth - 1);
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
return { modules, headerPaths };
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
function canonicalPath(filePath) {
|
|
1807
|
+
try {
|
|
1808
|
+
return realpathSync(filePath);
|
|
1809
|
+
} catch (_e) {
|
|
1810
|
+
return filePath;
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
function isWithinDir(dir, filePath) {
|
|
1815
|
+
if (!dir || !filePath) {
|
|
1816
|
+
return false;
|
|
1817
|
+
}
|
|
1818
|
+
const rel = relative(dir, filePath);
|
|
1819
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
/**
|
|
1823
|
+
* Attribute a source, module map, or header path to the package that owns
|
|
1824
|
+
* it: the dependency whose directory contains it (the most specific one
|
|
1825
|
+
* wins), the Swift Build intermediates directory named after the package, or
|
|
1826
|
+
* the root package.
|
|
1827
|
+
*
|
|
1828
|
+
* @param {String} filePath Absolute path
|
|
1829
|
+
* @param {Object} workspace `{ basePath, packages }` with canonical directories
|
|
1830
|
+
* @returns {undefined|Object} Owning package, `workspace.root` for the root package
|
|
1831
|
+
*/
|
|
1832
|
+
function owningPackage(filePath, workspace) {
|
|
1833
|
+
const target = canonicalPath(filePath);
|
|
1834
|
+
let best;
|
|
1835
|
+
for (const apkg of workspace.packages) {
|
|
1836
|
+
if (
|
|
1837
|
+
apkg.canonicalDir &&
|
|
1838
|
+
isWithinDir(apkg.canonicalDir, target) &&
|
|
1839
|
+
(!best || apkg.canonicalDir.length > best.canonicalDir.length)
|
|
1840
|
+
) {
|
|
1841
|
+
best = apkg;
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
if (best) {
|
|
1845
|
+
return best;
|
|
1846
|
+
}
|
|
1847
|
+
// Swift Build keeps generated module maps in
|
|
1848
|
+
// `.build/out/Intermediates.noindex/<package identity>.build/`
|
|
1849
|
+
const segments = target.split(PATH_SEPARATORS);
|
|
1850
|
+
const intermediates = segments.indexOf("Intermediates.noindex");
|
|
1851
|
+
if (intermediates >= 0 && segments[intermediates + 1]?.endsWith(".build")) {
|
|
1852
|
+
const identity = segments[intermediates + 1].slice(0, -".build".length);
|
|
1853
|
+
const byIdentity = workspace.packages.find(
|
|
1854
|
+
(p) => p.identity === identity.toLowerCase() || p.name === identity,
|
|
1855
|
+
);
|
|
1856
|
+
if (byIdentity) {
|
|
1857
|
+
return byIdentity;
|
|
1858
|
+
}
|
|
1859
|
+
if (identity === workspace.root.name) {
|
|
1860
|
+
return workspace.root;
|
|
1861
|
+
}
|
|
1862
|
+
return undefined;
|
|
1863
|
+
}
|
|
1864
|
+
if (isWithinDir(workspace.canonicalBasePath, target)) {
|
|
1865
|
+
const rel = relative(workspace.canonicalBasePath, target);
|
|
1866
|
+
return rel.split(PATH_SEPARATORS)[0] === ".build"
|
|
1867
|
+
? undefined
|
|
1868
|
+
: workspace.root;
|
|
1869
|
+
}
|
|
1870
|
+
return undefined;
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
/**
|
|
1874
|
+
* Build a description of every module in a completed swift build: its
|
|
1875
|
+
* compiler arguments, sources, and owning package.
|
|
1876
|
+
*
|
|
1877
|
+
* Arguments come from the llbuild build description when present and from
|
|
1878
|
+
* the verbose build output otherwise (the only source for the Swift Build
|
|
1879
|
+
* engine); sources come from the same places or from the output file maps.
|
|
1880
|
+
* Packages come from `workspace-state.json`, and every module is attributed
|
|
1881
|
+
* to the package whose directory holds its sources. Clang modules are
|
|
1882
|
+
* discovered through their module maps.
|
|
1883
|
+
*
|
|
1884
|
+
* @param {String} basePath Root package directory
|
|
1885
|
+
* @param {String} buildOutput Verbose build output
|
|
1886
|
+
* @param {Object} options CLI options
|
|
1887
|
+
* @param {Object} [rootMetadata] Parsed dump-package of the root package
|
|
1888
|
+
* @returns {Object} `{ modules, packages, root, clangModules, fileMaps }`
|
|
1889
|
+
*/
|
|
1890
|
+
export function collectSwiftBuildPlan(
|
|
1891
|
+
basePath,
|
|
1892
|
+
buildOutput,
|
|
1893
|
+
options,
|
|
1894
|
+
rootMetadata,
|
|
1895
|
+
) {
|
|
1896
|
+
const buildArtifacts = getAllFiles(
|
|
671
1897
|
basePath,
|
|
672
|
-
|
|
1898
|
+
`.build/**/{description.json,output-file-map.json,*${SWIFT_BUILD_FILEMAP_SUFFIX},module.modulemap}`,
|
|
673
1899
|
options,
|
|
674
1900
|
);
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
}
|
|
678
|
-
const
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
//
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
if (
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
1901
|
+
const fileMaps = [];
|
|
1902
|
+
const moduleMapFiles = new Set();
|
|
1903
|
+
const modules = {};
|
|
1904
|
+
const seenDescriptions = new Set();
|
|
1905
|
+
for (const artifact of buildArtifacts) {
|
|
1906
|
+
const name = basename(artifact);
|
|
1907
|
+
// Dependency repositories may ship build files of their own as test
|
|
1908
|
+
// fixtures; only their module maps describe real clang modules
|
|
1909
|
+
const buildSubdir = relative(join(basePath, ".build"), artifact).split(
|
|
1910
|
+
PATH_SEPARATORS,
|
|
1911
|
+
)[0];
|
|
1912
|
+
if (
|
|
1913
|
+
name !== "module.modulemap" &&
|
|
1914
|
+
["checkouts", "repositories", "registry"].includes(buildSubdir)
|
|
1915
|
+
) {
|
|
1916
|
+
continue;
|
|
1917
|
+
}
|
|
1918
|
+
if (name === "description.json") {
|
|
1919
|
+
const realDescription = canonicalPath(artifact);
|
|
1920
|
+
if (
|
|
1921
|
+
seenDescriptions.has(realDescription) ||
|
|
1922
|
+
!artifact.toLowerCase().split(PATH_SEPARATORS).includes("debug")
|
|
1923
|
+
) {
|
|
1924
|
+
continue;
|
|
1925
|
+
}
|
|
1926
|
+
seenDescriptions.add(realDescription);
|
|
1927
|
+
try {
|
|
1928
|
+
Object.assign(
|
|
1929
|
+
modules,
|
|
1930
|
+
parseSwiftBuildDescription(
|
|
1931
|
+
JSON.parse(readFileSync(artifact, { encoding: "utf-8" })),
|
|
1932
|
+
),
|
|
694
1933
|
);
|
|
1934
|
+
} catch (_e) {
|
|
1935
|
+
// Fall back to the verbose build output
|
|
695
1936
|
}
|
|
696
|
-
|
|
1937
|
+
} else if (name === "module.modulemap") {
|
|
1938
|
+
moduleMapFiles.add(artifact);
|
|
1939
|
+
} else {
|
|
1940
|
+
fileMaps.push(artifact);
|
|
697
1941
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
1942
|
+
}
|
|
1943
|
+
for (const [moduleName, invocation] of Object.entries(
|
|
1944
|
+
parseSwiftModuleInvocations(buildOutput),
|
|
1945
|
+
)) {
|
|
1946
|
+
if (!modules[moduleName]) {
|
|
1947
|
+
modules[moduleName] = invocation;
|
|
1948
|
+
} else if (!modules[moduleName].sources.length) {
|
|
1949
|
+
modules[moduleName].sources = invocation.sources;
|
|
701
1950
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
1951
|
+
}
|
|
1952
|
+
// Output file maps list the exact sources of every module
|
|
1953
|
+
for (const afilemap of fileMaps) {
|
|
1954
|
+
let metadata;
|
|
1955
|
+
try {
|
|
1956
|
+
metadata = parseOutputFileMap(afilemap);
|
|
1957
|
+
} catch (_e) {
|
|
1958
|
+
continue;
|
|
1959
|
+
}
|
|
1960
|
+
if (!metadata?.moduleName || !metadata.sourceFiles.length) {
|
|
1961
|
+
continue;
|
|
1962
|
+
}
|
|
1963
|
+
const amodule = modules[metadata.moduleName];
|
|
1964
|
+
if (!amodule) {
|
|
1965
|
+
modules[metadata.moduleName] = {
|
|
1966
|
+
args: [],
|
|
1967
|
+
sources: metadata.sourceFiles,
|
|
1968
|
+
isDriver: false,
|
|
1969
|
+
};
|
|
1970
|
+
} else if (!amodule.sources.length) {
|
|
1971
|
+
amodule.sources = metadata.sourceFiles;
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
const rootName = rootMetadata?.rootModule || basename(basePath);
|
|
1975
|
+
const workspace = {
|
|
1976
|
+
basePath,
|
|
1977
|
+
canonicalBasePath: canonicalPath(basePath),
|
|
1978
|
+
root: { identity: rootName.toLowerCase(), name: rootName, root: true },
|
|
1979
|
+
packages: [],
|
|
1980
|
+
};
|
|
1981
|
+
const workspaceStateFile = join(basePath, ".build", "workspace-state.json");
|
|
1982
|
+
if (safeExistsSync(workspaceStateFile)) {
|
|
1983
|
+
try {
|
|
1984
|
+
workspace.packages = parseSwiftWorkspaceState(
|
|
1985
|
+
JSON.parse(readFileSync(workspaceStateFile, { encoding: "utf-8" })),
|
|
1986
|
+
basePath,
|
|
705
1987
|
);
|
|
706
|
-
|
|
707
|
-
|
|
1988
|
+
} catch (_e) {
|
|
1989
|
+
workspace.packages = [];
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
for (const apkg of workspace.packages) {
|
|
1993
|
+
apkg.canonicalDir = apkg.dir ? canonicalPath(apkg.dir) : undefined;
|
|
1994
|
+
apkg.modules = new Set();
|
|
1995
|
+
}
|
|
1996
|
+
workspace.root.modules = new Set();
|
|
1997
|
+
const testModules = new Set(rootMetadata?.testModules || []);
|
|
1998
|
+
for (const [moduleName, amodule] of Object.entries(modules)) {
|
|
1999
|
+
const firstSource =
|
|
2000
|
+
amodule.sources.find(
|
|
2001
|
+
(s) => !s.split(PATH_SEPARATORS).includes(".build"),
|
|
2002
|
+
) || amodule.sources[0];
|
|
2003
|
+
const owner = firstSource
|
|
2004
|
+
? owningPackage(firstSource, workspace)
|
|
2005
|
+
: undefined;
|
|
2006
|
+
amodule.package = owner?.root ? workspace.root.identity : owner?.identity;
|
|
2007
|
+
amodule.isRoot = Boolean(owner?.root);
|
|
2008
|
+
// Local packages kept inside the scanned directory (monorepos) are part
|
|
2009
|
+
// of the project's own code
|
|
2010
|
+
amodule.isWorkspace =
|
|
2011
|
+
amodule.isRoot ||
|
|
2012
|
+
Boolean(
|
|
2013
|
+
owner?.kind === "fileSystem" &&
|
|
2014
|
+
owner.canonicalDir &&
|
|
2015
|
+
isWithinDir(workspace.canonicalBasePath, owner.canonicalDir),
|
|
708
2016
|
);
|
|
2017
|
+
amodule.isTest =
|
|
2018
|
+
testModules.has(moduleName) ||
|
|
2019
|
+
isTestOrAggregateModule(moduleName) ||
|
|
2020
|
+
(amodule.sources.length > 0 &&
|
|
2021
|
+
amodule.sources.every((s) =>
|
|
2022
|
+
relative(basePath, s).split(PATH_SEPARATORS).includes("Tests"),
|
|
2023
|
+
));
|
|
2024
|
+
owner?.modules.add(moduleName);
|
|
2025
|
+
// Module maps referenced through -fmodule-map-file describe clang modules
|
|
2026
|
+
for (let i = 0; i < amodule.args.length; i++) {
|
|
2027
|
+
if (
|
|
2028
|
+
amodule.args[i] === "-Xcc" &&
|
|
2029
|
+
amodule.args[i + 1]?.startsWith("-fmodule-map-file=")
|
|
2030
|
+
) {
|
|
2031
|
+
moduleMapFiles.add(
|
|
2032
|
+
amodule.args[i + 1].slice("-fmodule-map-file=".length),
|
|
2033
|
+
);
|
|
2034
|
+
}
|
|
709
2035
|
}
|
|
710
2036
|
}
|
|
711
|
-
//
|
|
712
|
-
|
|
713
|
-
|
|
2037
|
+
// The Swift Build project model names the modules of every package, also
|
|
2038
|
+
// those a failed build never compiled
|
|
2039
|
+
const manifestFile = join(basePath, ".build", "manifest.pif");
|
|
2040
|
+
if (safeExistsSync(manifestFile)) {
|
|
2041
|
+
let manifestModules = {};
|
|
2042
|
+
try {
|
|
2043
|
+
manifestModules = parseSwiftBuildManifest(
|
|
2044
|
+
JSON.parse(readFileSync(manifestFile, { encoding: "utf-8" })),
|
|
2045
|
+
);
|
|
2046
|
+
} catch (_e) {
|
|
2047
|
+
manifestModules = {};
|
|
2048
|
+
}
|
|
2049
|
+
for (const [identity, moduleNames] of Object.entries(manifestModules)) {
|
|
2050
|
+
const owner =
|
|
2051
|
+
workspace.packages.find((p) => p.identity === identity) ||
|
|
2052
|
+
workspace.root;
|
|
2053
|
+
for (const moduleName of moduleNames) {
|
|
2054
|
+
const attributed =
|
|
2055
|
+
modules[moduleName]?.package !== undefined ||
|
|
2056
|
+
[workspace.root, ...workspace.packages].some((p) =>
|
|
2057
|
+
p.modules.has(moduleName),
|
|
2058
|
+
);
|
|
2059
|
+
if (!attributed) {
|
|
2060
|
+
owner.modules.add(moduleName);
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
714
2064
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
// Our attempt to build must have yielded some output file maps.
|
|
719
|
-
// These can be used to understand the symbols offered by each of the dependency
|
|
720
|
-
const buildSymbols = collectBuildSymbols(basePath, options);
|
|
721
|
-
// Now let's attempt to learn about each module (internal and external) in detail
|
|
722
|
-
// Information about the classes, protocols, enums, and methods exported by each module is valuable
|
|
723
|
-
const moduleInfos = {};
|
|
724
|
-
const allModules = new Set([
|
|
725
|
-
packageMetadata?.rootModule.trim(),
|
|
726
|
-
...(packageMetadata?.dependencies || []).map((d) => d.ref.trim()),
|
|
727
|
-
...Object.keys(buildSymbols),
|
|
728
|
-
]);
|
|
729
|
-
for (const moduleName of Array.from(allModules)) {
|
|
730
|
-
// Skip the testing modules
|
|
731
|
-
if (
|
|
732
|
-
!moduleName ||
|
|
733
|
-
moduleName?.length === 0 ||
|
|
734
|
-
moduleName?.endsWith("Tests")
|
|
735
|
-
) {
|
|
2065
|
+
const clangModules = {};
|
|
2066
|
+
for (const moduleMapFile of moduleMapFiles) {
|
|
2067
|
+
if (!safeExistsSync(moduleMapFile)) {
|
|
736
2068
|
continue;
|
|
737
2069
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
console.log(
|
|
743
|
-
"Unable to obtain the semantic context for the module",
|
|
744
|
-
moduleName,
|
|
2070
|
+
let parsed;
|
|
2071
|
+
try {
|
|
2072
|
+
parsed = parseClangModuleMap(
|
|
2073
|
+
readFileSync(moduleMapFile, { encoding: "utf-8" }),
|
|
745
2074
|
);
|
|
2075
|
+
} catch (_e) {
|
|
2076
|
+
continue;
|
|
2077
|
+
}
|
|
2078
|
+
// Generated module maps live in the build directory; their headers point
|
|
2079
|
+
// back at the package sources
|
|
2080
|
+
const attributionPath = moduleMapFile
|
|
2081
|
+
.split(PATH_SEPARATORS)
|
|
2082
|
+
.includes(".build")
|
|
2083
|
+
? parsed.headerPaths.find((h) => isAbsolute(h)) || moduleMapFile
|
|
2084
|
+
: moduleMapFile;
|
|
2085
|
+
const owner = owningPackage(attributionPath, workspace);
|
|
2086
|
+
if (!owner) {
|
|
2087
|
+
continue;
|
|
2088
|
+
}
|
|
2089
|
+
for (const clangModule of parsed.modules) {
|
|
2090
|
+
if (!modules[clangModule] && !clangModules[clangModule]) {
|
|
2091
|
+
clangModules[clangModule] = owner.root
|
|
2092
|
+
? workspace.root.identity
|
|
2093
|
+
: owner.identity;
|
|
2094
|
+
owner.modules.add(clangModule);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
return {
|
|
2099
|
+
modules,
|
|
2100
|
+
clangModules,
|
|
2101
|
+
root: workspace.root,
|
|
2102
|
+
packages: workspace.packages,
|
|
2103
|
+
fileMaps,
|
|
2104
|
+
};
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
/**
|
|
2108
|
+
* Register a package's modules under its identity, name, and repository name
|
|
2109
|
+
* (and their lowercase forms) so that SBOM components can be matched
|
|
2110
|
+
* regardless of how they were named.
|
|
2111
|
+
*
|
|
2112
|
+
* @param {Object} packageModules Map to mutate
|
|
2113
|
+
* @param {Object} apkg Package with `identity`, `name`, `location`, `modules`
|
|
2114
|
+
*/
|
|
2115
|
+
function addPackageModuleAliases(packageModules, apkg) {
|
|
2116
|
+
const modules = Array.from(apkg.modules || []).sort();
|
|
2117
|
+
if (!modules.length) {
|
|
2118
|
+
return;
|
|
2119
|
+
}
|
|
2120
|
+
const repoName = (apkg.location || "")
|
|
2121
|
+
.split("?")[0]
|
|
2122
|
+
.split("#")[0]
|
|
2123
|
+
.replace(/\/+$/, "")
|
|
2124
|
+
.split("/")
|
|
2125
|
+
.pop()
|
|
2126
|
+
?.replace(/\.git$/, "");
|
|
2127
|
+
for (const alias of [apkg.identity, apkg.name, repoName]) {
|
|
2128
|
+
for (const key of [alias, alias?.toLowerCase()]) {
|
|
2129
|
+
if (key) {
|
|
2130
|
+
packageModules[key] = Array.from(
|
|
2131
|
+
new Set([...(packageModules[key] || []), ...modules]),
|
|
2132
|
+
).sort();
|
|
2133
|
+
}
|
|
746
2134
|
}
|
|
747
2135
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
/**
|
|
2139
|
+
* Check that sourcekitten can load SourceKit, with a trivial syntax request.
|
|
2140
|
+
*
|
|
2141
|
+
* A missing binary, a binary without its Swift runtime (`error while loading
|
|
2142
|
+
* shared libraries: libswiftCore.so`), or one linked against a different
|
|
2143
|
+
* Swift runtime than the toolchain provides (Linux has no stable Swift ABI, so
|
|
2144
|
+
* such a binary crashes inside libswiftCore) fails every request; the probe
|
|
2145
|
+
* turns that into one clear message instead of silent empty results.
|
|
2146
|
+
*
|
|
2147
|
+
* @param {Object} [env] Environment overrides for sourcekitten
|
|
2148
|
+
* @returns {boolean} `true` when sourcekitten answered the probe
|
|
2149
|
+
*/
|
|
2150
|
+
export function isSourcekittenUsable(env) {
|
|
2151
|
+
const status = probeSourcekitten(env);
|
|
2152
|
+
if (status === "ok") {
|
|
2153
|
+
return true;
|
|
2154
|
+
}
|
|
2155
|
+
if (status === "unavailable") {
|
|
2156
|
+
// cdxgen-plugins-bin ships sourcekitten for macOS only: a prebuilt Linux
|
|
2157
|
+
// binary runs only next to the Swift toolchain that built it.
|
|
2158
|
+
console.log(
|
|
2159
|
+
process.platform === "darwin"
|
|
2160
|
+
? "sourcekitten is unavailable, so Swift evidence is limited to import declarations. Install the cdxgen-plugins-bin package for this platform or set SOURCEKITTEN_CMD."
|
|
2161
|
+
: "sourcekitten is unavailable, so Swift evidence is limited to import declarations. Build sourcekitten with the Swift toolchain used here and set SOURCEKITTEN_CMD, or use a cdxgen container image that includes Swift.",
|
|
2162
|
+
);
|
|
2163
|
+
return false;
|
|
2164
|
+
}
|
|
2165
|
+
if (status === "not-found") {
|
|
2166
|
+
console.log(
|
|
2167
|
+
readEnvironmentVariable("SOURCEKITTEN_CMD")
|
|
2168
|
+
? "SOURCEKITTEN_CMD does not name an executable that exists, so Swift evidence is limited to import declarations. Point it at a sourcekitten binary."
|
|
2169
|
+
: "The bundled sourcekitten executable is missing, so Swift evidence is limited to import declarations. Reinstall the cdxgen-plugins-bin package for this platform or set SOURCEKITTEN_CMD.",
|
|
2170
|
+
);
|
|
2171
|
+
return false;
|
|
2172
|
+
}
|
|
2173
|
+
console.log(
|
|
2174
|
+
"sourcekitten could not load SourceKit, so Swift evidence is limited to import declarations. On Linux, sourcekitten must be linked against the Swift runtime of this toolchain; set SOURCEKITTEN_CMD to a sourcekitten built with it (or with --static-swift-stdlib). Set CDXGEN_DEBUG_MODE=debug to see the error.",
|
|
2175
|
+
);
|
|
2176
|
+
return false;
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
/**
|
|
2180
|
+
* Create a precise semantics slices file for a swift project.
|
|
2181
|
+
*
|
|
2182
|
+
* The project is built once in verbose mode. Every source file of the root
|
|
2183
|
+
* package's (non-test) modules is then indexed with sourcekitten using that
|
|
2184
|
+
* module's own compiler arguments and source list, with sourcekitten aligned
|
|
2185
|
+
* to the toolchain that compiled the project. The declaring module of every
|
|
2186
|
+
* resolved reference is recovered from its USR, which attributes each usage
|
|
2187
|
+
* to the package that provides the module (`moduleReferences`).
|
|
2188
|
+
*
|
|
2189
|
+
* @param basePath basePath Path
|
|
2190
|
+
* @param options options CLI options
|
|
2191
|
+
*/
|
|
2192
|
+
export function createSemanticsSlices(basePath, options) {
|
|
2193
|
+
const pkgSwiftFiles = getAllFiles(
|
|
2194
|
+
basePath,
|
|
2195
|
+
`${options.multiProject ? "**/" : ""}Package*.swift`,
|
|
2196
|
+
options,
|
|
2197
|
+
);
|
|
2198
|
+
if (!pkgSwiftFiles.length) {
|
|
2199
|
+
return undefined;
|
|
2200
|
+
}
|
|
2201
|
+
const toolchain = resolveSwiftToolchain(basePath);
|
|
2202
|
+
const sourcekittenEnv = sourcekittenEnvForToolchain(toolchain);
|
|
2203
|
+
// Without a working sourcekitten, the evidence is limited to the import
|
|
2204
|
+
// declarations of the modules the build describes
|
|
2205
|
+
const useSourcekitten = isSourcekittenUsable(sourcekittenEnv);
|
|
2206
|
+
if (DEBUG_MODE && toolchain) {
|
|
2207
|
+
console.log(
|
|
2208
|
+
`Swift toolchain for ${basePath}: ${toolchain.compilerVersion} at ${toolchain.toolchainDir}.`,
|
|
2209
|
+
sourcekittenEnv
|
|
2210
|
+
? `sourcekitten environment: ${JSON.stringify(sourcekittenEnv)}`
|
|
2211
|
+
: "",
|
|
2212
|
+
);
|
|
2213
|
+
}
|
|
2214
|
+
// Package.swift contains the root targets, including which ones are tests
|
|
2215
|
+
const packageMetadata = parseDumpPackage(dumpPackage(basePath));
|
|
2216
|
+
const userCompilerArgs = process?.env?.SWIFT_COMPILER_ARGS;
|
|
2217
|
+
const userSdkArgs = process?.env?.SWIFT_SDK_ARGS;
|
|
2218
|
+
// A clean verbose debug build reveals the compiler arguments of every module
|
|
2219
|
+
const buildOutput =
|
|
2220
|
+
userCompilerArgs && userSdkArgs ? undefined : verboseBuild(basePath);
|
|
2221
|
+
const buildPlan = collectSwiftBuildPlan(
|
|
2222
|
+
basePath,
|
|
2223
|
+
buildOutput,
|
|
2224
|
+
options,
|
|
2225
|
+
packageMetadata,
|
|
2226
|
+
);
|
|
2227
|
+
const packageModules = {};
|
|
2228
|
+
for (const apkg of [buildPlan.root, ...buildPlan.packages]) {
|
|
2229
|
+
addPackageModuleAliases(packageModules, apkg);
|
|
2230
|
+
}
|
|
2231
|
+
const packages = buildPlan.packages
|
|
2232
|
+
.filter((p) => p.modules.size)
|
|
2233
|
+
.map((p) => ({
|
|
2234
|
+
identity: p.identity,
|
|
2235
|
+
name: p.name,
|
|
2236
|
+
location: p.location,
|
|
2237
|
+
kind: p.kind,
|
|
2238
|
+
modules: Array.from(p.modules).sort(),
|
|
2239
|
+
}));
|
|
2240
|
+
const dependencyModules = new Set(
|
|
2241
|
+
buildPlan.packages.flatMap((p) => Array.from(p.modules)),
|
|
2242
|
+
);
|
|
2243
|
+
const rootModuleNames = Object.keys(buildPlan.modules).filter(
|
|
2244
|
+
(m) => buildPlan.modules[m].isWorkspace && !buildPlan.modules[m].isTest,
|
|
2245
|
+
);
|
|
2246
|
+
// A private module cache per run: the build's cache is shared with every
|
|
2247
|
+
// compiler that ever touched the project, and modules from a mismatched
|
|
2248
|
+
// toolchain make SourceKit return empty results
|
|
2249
|
+
const moduleCachePath = safeMkdtempSync(join(getTmpDir(), "swiftsem-cache-"));
|
|
751
2250
|
const fileIndexes = {};
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
2251
|
+
const indexedFiles = new Set();
|
|
2252
|
+
const indexFile = (afile, compilerArgs) => {
|
|
2253
|
+
fileIndexes[afile] = useSourcekitten
|
|
2254
|
+
? parseIndex(index(afile, compilerArgs, sourcekittenEnv))
|
|
2255
|
+
: undefined;
|
|
2256
|
+
indexedFiles.add(canonicalPath(afile));
|
|
2257
|
+
};
|
|
2258
|
+
const isIndexableSource = (afile) =>
|
|
2259
|
+
afile.endsWith(".swift") &&
|
|
2260
|
+
!/^Package(@.+)?\.swift$/.test(basename(afile)) &&
|
|
2261
|
+
!relative(basePath, afile).split(PATH_SEPARATORS).includes(".build") &&
|
|
2262
|
+
safeExistsSync(afile);
|
|
2263
|
+
try {
|
|
2264
|
+
const moduleArgs = {};
|
|
2265
|
+
for (const moduleName of rootModuleNames) {
|
|
2266
|
+
const amodule = buildPlan.modules[moduleName];
|
|
2267
|
+
moduleArgs[moduleName] = [
|
|
2268
|
+
...(userCompilerArgs
|
|
2269
|
+
? splitSwiftCommandArgs(`${userCompilerArgs} ${userSdkArgs || ""}`)
|
|
2270
|
+
: amodule.args),
|
|
2271
|
+
"-module-cache-path",
|
|
2272
|
+
moduleCachePath,
|
|
2273
|
+
];
|
|
2274
|
+
if (!moduleArgs[moduleName].includes("-module-name")) {
|
|
2275
|
+
moduleArgs[moduleName].push("-module-name", moduleName);
|
|
2276
|
+
}
|
|
2277
|
+
// Every source of the module is passed so that references to
|
|
2278
|
+
// declarations in the module's other files (including generated
|
|
2279
|
+
// sources) resolve; only the project's own files are indexed
|
|
2280
|
+
const moduleSources = amodule.sources.filter((s) => safeExistsSync(s));
|
|
2281
|
+
for (const afile of moduleSources.filter(isIndexableSource)) {
|
|
2282
|
+
indexFile(afile, [...moduleArgs[moduleName], ...moduleSources]);
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
// Sources that belong to no known module, for example when the build
|
|
2286
|
+
// failed, are indexed with the merged arguments of the root modules
|
|
2287
|
+
const fallbackArgs = userCompilerArgs
|
|
2288
|
+
? splitSwiftCommandArgs(`${userCompilerArgs} ${userSdkArgs || ""}`)
|
|
2289
|
+
: extractCompilerParamsFromBuild(
|
|
2290
|
+
buildOutput,
|
|
2291
|
+
packageMetadata?.rootModules,
|
|
2292
|
+
).compilerArgs;
|
|
2293
|
+
// Sources of test and dependency modules are never indexed
|
|
2294
|
+
const plannedSources = new Set();
|
|
2295
|
+
for (const amodule of Object.values(buildPlan.modules)) {
|
|
2296
|
+
for (const asource of amodule.sources) {
|
|
2297
|
+
plannedSources.add(canonicalPath(asource));
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
for (const afile of getAllFiles(basePath, "**/*.swift", options)) {
|
|
2301
|
+
const canonicalFile = canonicalPath(afile);
|
|
2302
|
+
if (
|
|
2303
|
+
indexedFiles.has(canonicalFile) ||
|
|
2304
|
+
plannedSources.has(canonicalFile) ||
|
|
2305
|
+
!isIndexableSource(afile) ||
|
|
2306
|
+
relative(basePath, afile)
|
|
2307
|
+
.split(PATH_SEPARATORS)
|
|
2308
|
+
.some((segment) => segment.endsWith("Tests"))
|
|
2309
|
+
) {
|
|
2310
|
+
continue;
|
|
2311
|
+
}
|
|
2312
|
+
indexFile(afile, [
|
|
2313
|
+
...fallbackArgs,
|
|
2314
|
+
"-module-cache-path",
|
|
2315
|
+
moduleCachePath,
|
|
2316
|
+
]);
|
|
2317
|
+
}
|
|
2318
|
+
// Attribute every resolved reference to its declaring module
|
|
2319
|
+
const allUsrs = new Set();
|
|
2320
|
+
for (const afileIndex of Object.values(fileIndexes)) {
|
|
2321
|
+
for (const ausr of Object.keys(afileIndex?.usrLines || {})) {
|
|
2322
|
+
allUsrs.add(ausr);
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
const usrModules = resolveUsrModules(Array.from(allUsrs), basePath);
|
|
2326
|
+
for (const afile of Object.keys(fileIndexes)) {
|
|
2327
|
+
const afileIndex = fileIndexes[afile] || {};
|
|
2328
|
+
const moduleLines = {};
|
|
2329
|
+
const addLines = (moduleName, lines) => {
|
|
2330
|
+
if (!dependencyModules.has(moduleName)) {
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2333
|
+
if (!moduleLines[moduleName]) {
|
|
2334
|
+
moduleLines[moduleName] = new Set();
|
|
2335
|
+
}
|
|
2336
|
+
for (const aline of lines) {
|
|
2337
|
+
moduleLines[moduleName].add(aline);
|
|
2338
|
+
}
|
|
2339
|
+
};
|
|
2340
|
+
for (const [ausr, lines] of Object.entries(afileIndex.usrLines || {})) {
|
|
2341
|
+
const moduleName = usrModules.get(ausr);
|
|
2342
|
+
if (moduleName) {
|
|
2343
|
+
addLines(moduleName, lines);
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
// Import declarations are evidence even when SourceKit could not load
|
|
2347
|
+
// the module, for example after a partial build
|
|
2348
|
+
let sourceText;
|
|
2349
|
+
try {
|
|
2350
|
+
sourceText = readFileSync(afile, { encoding: "utf-8" });
|
|
2351
|
+
} catch (_e) {
|
|
2352
|
+
sourceText = "";
|
|
2353
|
+
}
|
|
2354
|
+
for (const aimport of parseSwiftImports(sourceText)) {
|
|
2355
|
+
addLines(aimport.module, [aimport.line]);
|
|
2356
|
+
}
|
|
2357
|
+
const moduleReferences = {};
|
|
2358
|
+
for (const moduleName of Object.keys(moduleLines).sort()) {
|
|
2359
|
+
moduleReferences[moduleName] = Array.from(moduleLines[moduleName]).sort(
|
|
2360
|
+
(a, b) => a - b,
|
|
2361
|
+
);
|
|
2362
|
+
}
|
|
2363
|
+
afileIndex.moduleReferences = moduleReferences;
|
|
2364
|
+
delete afileIndex.usrLines;
|
|
2365
|
+
fileIndexes[afile] = afileIndex;
|
|
756
2366
|
}
|
|
757
|
-
|
|
758
|
-
|
|
2367
|
+
} finally {
|
|
2368
|
+
safeRmSync(moduleCachePath, { recursive: true, force: true });
|
|
759
2369
|
}
|
|
760
2370
|
return {
|
|
2371
|
+
projectPath: basePath,
|
|
2372
|
+
toolchain: toolchain?.compilerVersion,
|
|
761
2373
|
packageMetadata,
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
2374
|
+
packages,
|
|
2375
|
+
packageModules,
|
|
2376
|
+
buildSymbols: collectBuildSymbols(basePath, options, buildPlan.fileMaps),
|
|
765
2377
|
fileIndexes,
|
|
766
2378
|
};
|
|
767
2379
|
}
|