File: VerifySourceCode.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (177 lines) | stat: -rw-r--r-- 5,769 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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import ArgumentParser
import Foundation
import RegexBuilder

fileprivate let modules: [String] = [
  "SwiftParser",
  "SwiftParserDiagnostics",
  "SwiftSyntax",
  "SwiftSyntaxBuilder",
]

struct VerifySourceCode: ParsableCommand {
  static let configuration = CommandConfiguration(
    abstract: "Verify that the generated sources match the ones checked into the repository."
  )

  @OptionGroup
  var arguments: SourceCodeGeneratorArguments

  func run() throws {
    try VerifySpiYmlExecutor().run()

    try VerifySourceCodeExecutor(
      toolchain: try arguments.toolchain,
      verbose: arguments.verbose
    ).run()
  }
}

struct VerifySourceCodeExecutor {
  /// The path to the toolchain that shall be used to build SwiftSyntax.
  private let toolchain: URL

  /// Enable verbose logging.
  private let verbose: Bool

  private let generateSourceCodeExecutor: GenerateSourceCodeExecutor

  /// Creates an executor
  /// - Parameters:
  ///   - toolchain: The path to the toolchain that shall be used to build SwiftSyntax.
  ///   - verbose: Enable verbose logging.
  init(toolchain: URL, verbose: Bool = false) {
    self.toolchain = toolchain
    self.verbose = verbose
    self.generateSourceCodeExecutor = GenerateSourceCodeExecutor(
      toolchain: toolchain,
      verbose: verbose
    )
  }

  func run() throws {
    try withTemporaryDirectory(verifyCodeGeneratedFiles(tempDir:))
  }

  private func verifyCodeGeneratedFiles(tempDir: URL) throws {
    try generateSourceCodeExecutor.run(sourceDir: tempDir)

    logSection("Verifing code generated files")

    for module in modules {
      let selfGeneratedDir = tempDir.appendingPathComponent(module).appendingPathComponent("generated")
      let userGeneratedDir = Paths.sourcesDir.appendingPathComponent(module).appendingPathComponent("generated")

      let process = ProcessRunner(
        executableURL: try Paths.diffExec,
        arguments: [
          "--recursive",
          "--exclude",
          ".*",  // Exclude dot files like .DS_Store
          "--context=0",
          selfGeneratedDir.path,
          userGeneratedDir.path,
        ]
      )

      let result = try process.run(verbose: verbose)

      if !result.stderr.isEmpty {
        throw ScriptExectutionError(
          message: """
            FAIL: code-generated files committed to repository do
            not match generated ones. Please re-generate the
            code-generated-files using the following command, open a PR to the
            SwiftSyntax project and merge it alongside the main PR.
            $ swift run swift-syntax-dev-utils generate-source-code
            /path/to/toolchain.xctoolchain/usr
            """
        )
      }
    }
  }
}

struct VerifySpiYmlExecutor {
  static let configuration = CommandConfiguration(
    abstract: "Verify that the .spi.yml file contains all libraries from Package.swift"
  )

  /// Returns all libraries declared in `Package.swift`.
  ///
  /// Note: It would be nice if we could compile Package.swift with this file and reallly
  /// inspect the package targets instead of doing regex scraping, but this is good enough
  /// for now.
  private func librariesInPackageManifest() throws -> [String] {
    let extractNameRegex = Regex {
      #/^.*/#
      #".library(name: ""#
      Capture(ZeroOrMore(.word))
      #"""#
      #/.*$/#
    }
    let packageFile = Paths.packageDir.appendingPathComponent("Package.swift")
    let packageFileContents = try String(contentsOf: packageFile)
    return
      packageFileContents
      .components(separatedBy: "\n")
      .filter({ !$0.matches(of: extractNameRegex).isEmpty })
      .map { $0.replacing(extractNameRegex) { $0.1 } }
      .filter({ !$0.hasPrefix("_") })
      .sorted()
  }
  /// Returns all targets listed in `.spi.yml`.
  ///
  /// Note: It would be nice to actually parse the .yml file but then we would need to add
  /// a dependency from this script on a YAML parser and that just doesn’t seem worth it.
  private func targetsInSwiftPackageIndexManifest() throws -> [String] {
    let extractTargetRegex = Regex {
      #/^      - /#
      Capture(ZeroOrMore(.word))
      #/$/#
    }
    let spiYmlFile = Paths.packageDir.appendingPathComponent(".spi.yml")
    let spiYmlFileContents = try String(contentsOf: spiYmlFile)
    return
      spiYmlFileContents
      .components(separatedBy: "\n")
      .filter({ !$0.matches(of: extractTargetRegex).isEmpty })
      .map { $0.replacing(extractTargetRegex) { $0.1 } }
      .sorted()
  }

  func run() throws {
    logSection("Verifing that .spi.yml is up-to-date")

    let difference = try targetsInSwiftPackageIndexManifest().difference(from: librariesInPackageManifest())

    if !difference.isEmpty {
      let differenceDescription = difference.map { change in
        switch change {
        case .insert(_, let element, _):
          return " - Unexpected in .spi.yml: \(element)"
        case .remove(_, let element, _):
          return " - Missing in .spi.yml: \(element)"
        }
      }.joined(separator: "\n")
      throw ScriptExectutionError(
        message: """
          .spi.yml did not contain the same libraries as Package.swift:
          \(differenceDescription)
          """
      )
    }
  }
}