Dangerfile.swift 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import Foundation
  2. import Danger
  3. fileprivate extension Danger.File {
  4. var isInSources: Bool { hasPrefix("Sources/") }
  5. var isInTests: Bool { hasPrefix("Tests/") }
  6. var isInDemos: Bool { hasPrefix("Demos/") }
  7. var isInBenchmarking: Bool { hasPrefix("Benchmarking/") }
  8. var isInVendor: Bool { contains("/Vendor/") }
  9. var isInFMDB: Bool { contains("/FMDB/") }
  10. var isSourceFile: Bool {
  11. fileType == .swift || fileType == .m || fileType == .mm || fileType == .h
  12. }
  13. private static let spmOnlyTargetNames: Set<String> = [
  14. "CocoaLumberjackSwiftLogBackend",
  15. ]
  16. var isSPMOnlySourceFile: Bool {
  17. guard isSourceFile else { return false }
  18. if isInSources {
  19. return Self.spmOnlyTargetNames.contains(where: { contains("/\($0)/") })
  20. } else if isInTests {
  21. return Self.spmOnlyTargetNames.contains(where: { contains("/\($0)Tests/") })
  22. }
  23. return false
  24. }
  25. var isSwiftPackageDefintion: Bool {
  26. hasPrefix("Package") && fileType == .swift
  27. }
  28. var isDangerfile: Bool {
  29. self == "Dangerfile.swift"
  30. }
  31. }
  32. let danger = Danger()
  33. let git = danger.git
  34. // Sometimes it's a README fix, or something like that
  35. let isDeclaredTrivial = danger.github?.pullRequest.title.contains("#trivial") ?? false
  36. let hasSourceChanges = (git.modifiedFiles + git.createdFiles).contains(where: \.isInSources)
  37. // Make it more obvious that a PR is a work in progress and shouldn't be merged yet
  38. if danger.github?.pullRequest.title.contains("WIP") == true && danger.github?.pullRequest.draft != true {
  39. warn("PR is marked as Work in Progress. Please consider marking the PR as Draft in GitHub to prevent merges.")
  40. }
  41. // Warn when there is a big PR
  42. if let additions = danger.github?.pullRequest.additions,
  43. let deletions = danger.github?.pullRequest.deletions,
  44. case let sum = additions + deletions, sum > 1000 {
  45. warn("Pull request is relatively big (\(sum) lines changed). If this PR contains multiple changes, consider splitting it into separate PRs for easier reviews.")
  46. }
  47. // Warn when library files has been updated but not tests.
  48. if hasSourceChanges && !git.modifiedFiles.contains(where: \.isInTests) {
  49. warn("The library files were changed, but the tests remained unmodified. Consider updating or adding to the tests to match the library changes.")
  50. }
  51. // Run SwiftLint
  52. SwiftLint.lint(.modifiedAndCreatedFiles(directory: "Sources"), inline: true)
  53. // Added (or removed) library files need to be added (or removed) from the
  54. // Carthage Xcode project to avoid breaking things for our Carthage users.
  55. let xcodeProjectFile: Danger.File = "Lumberjack.xcodeproj/project.pbxproj"
  56. let xcodeProjectWasModified = git.modifiedFiles.contains(xcodeProjectFile)
  57. if (git.createdFiles + git.deletedFiles).contains(where: { $0.isInSources && $0.isSourceFile && !$0.isSPMOnlySourceFile })
  58. && !xcodeProjectWasModified {
  59. fail("Added or removed library files require the Carthage Xcode project to be updated.")
  60. }
  61. // Check xcodeproj settings are not changed
  62. // Check to see if any of our project files contains a line with "SOURCE_ROOT" which indicates that the file isn't in sync with Finder.
  63. if xcodeProjectWasModified {
  64. let acceptedSettings: Set<String> = [
  65. "APPLICATION_EXTENSION_API_ONLY",
  66. "ASSETCATALOG_COMPILER_APPICON_NAME",
  67. "ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME",
  68. "ATTRIBUTES",
  69. "CODE_SIGN_IDENTITY",
  70. "COMBINE_HIDPI_IMAGES",
  71. "FRAMEWORK_VERSION",
  72. "GCC_PRECOMPILE_PREFIX_HEADER",
  73. "GCC_PREFIX_HEADER",
  74. "IBSC_MODULE",
  75. "INFOPLIST_FILE",
  76. "MODULEMAP_FILE",
  77. "PRIVATE_HEADERS_FOLDER_PATH",
  78. "PRODUCT_BUNDLE_IDENTIFIER",
  79. "PRODUCT_NAME",
  80. "PUBLIC_HEADERS_FOLDER_PATH",
  81. "SDKROOT",
  82. "SUPPORTED_PLATFORMS",
  83. "TARGETED_DEVICE_FAMILY",
  84. "WRAPPER_EXTENSION",
  85. ]
  86. [xcodeProjectFile]
  87. .lazy
  88. .filter { FileManager.default.fileExists(atPath: $0) }
  89. .forEach { projectFile in
  90. danger.utils.readFile(projectFile).split(separator: "\n").enumerated().forEach { (offset, line) in
  91. if line.contains("sourceTree = SOURCE_ROOT;") &&
  92. line.contains("PBXFileReference") &&
  93. !line.contains("path = Sources/CocoaLumberjackSwiftSupport/include/") {
  94. warn(message: "Files should be in sync with project structure", file: projectFile, line: offset + 1)
  95. }
  96. if let range = line.range(of: "[A-Z_]+ = .*;", options: .regularExpression) {
  97. let setting = String(line[range].prefix(while: { $0 != " " }))
  98. if !acceptedSettings.contains(setting) {
  99. warn(message: "Xcode settings need to remain in Configs/*.xcconfig. Please move " + setting + " to the xcconfig file", file: projectFile, line: offset + 1)
  100. }
  101. }
  102. }
  103. }
  104. }
  105. // Check Copyright
  106. let copyrightLines = (
  107. source: [
  108. "// Software License Agreement (BSD License)",
  109. "//",
  110. "// Copyright (c) 2010-2025, Deusty, LLC",
  111. "// All rights reserved.",
  112. "//",
  113. "// Redistribution and use of this software in source and binary forms,",
  114. "// with or without modification, are permitted provided that the following conditions are met:",
  115. "//",
  116. "// * Redistributions of source code must retain the above copyright notice,",
  117. "// this list of conditions and the following disclaimer.",
  118. "//",
  119. "// * Neither the name of Deusty nor the names of its contributors may be used",
  120. "// to endorse or promote products derived from this software without specific",
  121. "// prior written permission of Deusty, LLC.",
  122. ],
  123. demos: [
  124. "//",
  125. "// ",
  126. "// ",
  127. "//",
  128. "// CocoaLumberjack Demos",
  129. "//",
  130. ],
  131. benchmarking: [
  132. "//",
  133. "// ",
  134. "// ",
  135. "//",
  136. "// CocoaLumberjack Benchmarking",
  137. "//",
  138. ]
  139. )
  140. // let sourcefilesToCheck = Dir.glob("*/*/*") // uncomment when we want to test all the files (locally)
  141. let sourcefilesToCheck = Set(git.modifiedFiles + git.createdFiles)
  142. let filesWithInvalidCopyright = sourcefilesToCheck.lazy
  143. .filter { $0.isSourceFile }
  144. .filter { !$0.isSwiftPackageDefintion }
  145. .filter { !$0.isDangerfile }
  146. .filter { !$0.isInVendor && !$0.isInFMDB }
  147. .filter { FileManager.default.fileExists(atPath: $0) }
  148. .filter {
  149. // Use correct copyright lines depending on source file location
  150. let (expectedLines, shouldMatchExactly): (Array<String>, Bool)
  151. if $0.isInDemos {
  152. expectedLines = copyrightLines.demos
  153. shouldMatchExactly = false
  154. } else if $0.isInBenchmarking {
  155. expectedLines = copyrightLines.benchmarking
  156. shouldMatchExactly = false
  157. } else {
  158. expectedLines = copyrightLines.source
  159. shouldMatchExactly = true
  160. }
  161. let actualLines = danger.utils.readFile($0).split(separator: "\n").lazy.map(String.init)
  162. if shouldMatchExactly {
  163. return !actualLines.starts(with: expectedLines)
  164. } else {
  165. return !zip(actualLines, expectedLines).allSatisfy { $0.starts(with: $1) }
  166. }
  167. }
  168. if !filesWithInvalidCopyright.isEmpty {
  169. filesWithInvalidCopyright.sorted().forEach {
  170. warn(message: "Invalid copyright!", file: $0, line: 1)
  171. }
  172. warn("""
  173. Copyright is not valid. See our default copyright in all of our files (Sources, Demos and Benchmarking use different formats).<br/>
  174. Invalid files:<br/>
  175. \(filesWithInvalidCopyright.map { "- \($0)" }.joined(separator: "<br/>\n"))
  176. """)
  177. }