File: PluginPaths.swift

package info (click to toggle)
swiftlang 6.2.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,856,264 kB
  • sloc: cpp: 9,995,718; ansic: 2,234,019; asm: 1,092,167; python: 313,940; objc: 82,726; f90: 80,126; lisp: 38,373; pascal: 25,580; sh: 20,378; ml: 5,058; perl: 4,751; makefile: 4,725; awk: 3,535; javascript: 3,018; xml: 918; fortran: 664; cs: 573; ruby: 396
file content (150 lines) | stat: -rw-r--r-- 5,810 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import Foundation
package import SourceKitD
import ToolchainRegistry

/// The path to the `SwiftSourceKitPluginTests` test bundle. This gives us a hook into the the build directory.
private let xctestBundle: URL = {
  #if canImport(Darwin)
  for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") {
    return bundle.bundleURL
  }
  preconditionFailure("Failed to find xctest bundle")
  #else
  return URL(
    fileURLWithPath: CommandLine.arguments.first!,
    relativeTo: URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
  )
  #endif
}()

/// When running tests from Xcode, determine the build configuration of the package.
var inferredXcodeBuildConfiguration: String? {
  if let xcodeBuildDirectory = ProcessInfo.processInfo.environment["__XCODE_BUILT_PRODUCTS_DIR_PATHS"] {
    return URL(fileURLWithPath: xcodeBuildDirectory).lastPathComponent
  } else {
    return nil
  }
}

/// Shorthand for `FileManager.fileExists`
private func fileExists(at url: URL) -> Bool {
  return FileManager.default.fileExists(atPath: url.path)
}

/// Try to find the client and server plugin relative to `base`.
///
/// Implementation detail of `sourceKitPluginPaths` which walks up the directory structure, repeatedly calling this method.
private func pluginPaths(relativeTo base: URL) -> PluginPaths? {
  // When building in Xcode
  if let buildConfiguration = inferredXcodeBuildConfiguration {
    let frameworksDir = base.appendingPathComponent("Products")
      .appendingPathComponent(buildConfiguration)
      .appendingPathComponent("PackageFrameworks")
    let clientPlugin =
      frameworksDir
      .appendingPathComponent("SwiftSourceKitClientPlugin.framework")
      .appendingPathComponent("SwiftSourceKitClientPlugin")
    let servicePlugin =
      frameworksDir
      .appendingPathComponent("SwiftSourceKitPlugin.framework")
      .appendingPathComponent("SwiftSourceKitPlugin")
    if fileExists(at: clientPlugin) && fileExists(at: servicePlugin) {
      return PluginPaths(clientPlugin: clientPlugin, servicePlugin: servicePlugin)
    }
  }

  // When creating an `xctestproducts` bundle
  do {
    let frameworksDir = base.appendingPathComponent("PackageFrameworks")
    let clientPlugin =
      frameworksDir
      .appendingPathComponent("SwiftSourceKitClientPlugin.framework")
      .appendingPathComponent("SwiftSourceKitClientPlugin")
    let servicePlugin =
      frameworksDir
      .appendingPathComponent("SwiftSourceKitPlugin.framework")
      .appendingPathComponent("SwiftSourceKitPlugin")
    if fileExists(at: clientPlugin) && fileExists(at: servicePlugin) {
      return PluginPaths(clientPlugin: clientPlugin, servicePlugin: servicePlugin)
    }
  }

  // When building using 'swift test'
  do {
    #if canImport(Darwin)
    let clientPluginName = "libSwiftSourceKitClientPlugin.dylib"
    let servicePluginName = "libSwiftSourceKitPlugin.dylib"
    #elseif os(Windows)
    let clientPluginName = "SwiftSourceKitClientPlugin.dll"
    let servicePluginName = "SwiftSourceKitPlugin.dll"
    #else
    let clientPluginName = "libSwiftSourceKitClientPlugin.so"
    let servicePluginName = "libSwiftSourceKitPlugin.so"
    #endif
    let clientPlugin = base.appendingPathComponent(clientPluginName)
    let servicePlugin = base.appendingPathComponent(servicePluginName)
    if fileExists(at: clientPlugin) && fileExists(at: servicePlugin) {
      return PluginPaths(clientPlugin: clientPlugin, servicePlugin: servicePlugin)
    }
  }

  return nil
}

/// Returns the paths from which the SourceKit plugins should be loaded or throws an error if the plugins cannot be
/// found.
package var sourceKitPluginPaths: PluginPaths {
  get async throws {
    struct PluginLoadingError: Error, CustomStringConvertible {
      let searchBase: URL
      var description: String {
        // We can't declare a dependency from the test *target* on the SourceKit plugin *product*
        // (https://github.com/swiftlang/swift-package-manager/issues/8245).
        // We thus require a build before running the tests to ensure the plugin dylibs are in the build products
        // folder.
        """
        Could not find SourceKit plugin. Ensure that you build the entire SourceKit-LSP package before running tests.

        Searching for plugin relative to \(searchBase)
        """
      }
    }

    var base: URL
    if let pluginPaths = ProcessInfo.processInfo.environment["SOURCEKIT_LSP_TEST_PLUGIN_PATHS"] {
      if pluginPaths == "RELATIVE_TO_SOURCEKITD" {
        let toolchain = await ToolchainRegistry.forTesting.default
        guard let clientPlugin = toolchain?.sourceKitClientPlugin, let servicePlugin = toolchain?.sourceKitServicePlugin
        else {
          throw PluginLoadingError(searchBase: URL(string: "relative-to-sourcekitd://")!)
        }
        return PluginPaths(clientPlugin: clientPlugin, servicePlugin: servicePlugin)
      } else {
        base = URL(fileURLWithPath: pluginPaths)
      }
    } else {
      base = xctestBundle
    }
    var searchPath = base
    while searchPath.pathComponents.count > 1 {
      if let paths = pluginPaths(relativeTo: searchPath) {
        return paths
      }
      searchPath = searchPath.deletingLastPathComponent()
    }

    throw PluginLoadingError(searchBase: base)
  }
}