// swift-tools-version: 5.9
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 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
import PackageDescription

var products: [Product] = [
  .executable(
    name: "swift-format",
    targets: ["swift-format"]
  ),
  .library(
    name: "SwiftFormat",
    targets: ["SwiftFormat"]
  ),
  .plugin(
    name: "FormatPlugin",
    targets: ["Format Source Code"]
  ),
  .plugin(
    name: "LintPlugin",
    targets: ["Lint Source Code"]
  ),
]

var targets: [Target] = [
  .target(
    name: "_SwiftFormatInstructionCounter",
    exclude: ["CMakeLists.txt"]
  ),

  .target(
    name: "SwiftFormat",
    dependencies: [
      .product(name: "Markdown", package: "swift-markdown")
    ]
      + swiftSyntaxDependencies([
        "SwiftOperators", "SwiftParser", "SwiftParserDiagnostics", "SwiftSyntax", "SwiftSyntaxBuilder",
      ]),
    exclude: ["CMakeLists.txt"]
  ),
  .target(
    name: "_SwiftFormatTestSupport",
    dependencies: [
      "SwiftFormat"
    ]
      + swiftSyntaxDependencies([
        "SwiftOperators", "SwiftParser", "SwiftParserDiagnostics", "SwiftSyntax", "SwiftSyntaxBuilder",
      ])
  ),
  .plugin(
    name: "Format Source Code",
    capability: .command(
      intent: .sourceCodeFormatting(),
      permissions: [
        .writeToPackageDirectory(reason: "This command formats the Swift source files")
      ]
    ),
    dependencies: [
      .target(name: "swift-format")
    ],
    path: "Plugins/FormatPlugin"
  ),
  .plugin(
    name: "Lint Source Code",
    capability: .command(
      intent: .custom(
        verb: "lint-source-code",
        description: "Lint source code for a specified target."
      )
    ),
    dependencies: [
      .target(name: "swift-format")
    ],
    path: "Plugins/LintPlugin"
  ),
  .executableTarget(
    name: "generate-swift-format",
    dependencies: [
      "SwiftFormat"
    ]
  ),
  .executableTarget(
    name: "swift-format",
    dependencies: [
      "_SwiftFormatInstructionCounter",
      "SwiftFormat",
      .product(name: "ArgumentParser", package: "swift-argument-parser"),
    ] + swiftSyntaxDependencies(["SwiftDiagnostics", "SwiftParser", "SwiftSyntax"]),
    exclude: ["CMakeLists.txt"],
    linkerSettings: swiftformatLinkSettings
  ),

  .testTarget(
    name: "SwiftFormatPerformanceTests",
    dependencies: [
      "SwiftFormat",
      "_SwiftFormatTestSupport",
    ] + swiftSyntaxDependencies(["SwiftParser", "SwiftSyntax"])
  ),
  .testTarget(
    name: "SwiftFormatTests",
    dependencies: [
      "SwiftFormat",
      "_SwiftFormatTestSupport",
      .product(name: "Markdown", package: "swift-markdown"),
    ] + swiftSyntaxDependencies(["SwiftOperators", "SwiftParser", "SwiftSyntax", "SwiftSyntaxBuilder"])
  ),
]

if buildOnlyTests {
  products = []
  targets = targets.compactMap { target in
    guard target.isTest || target.name == "_SwiftFormatTestSupport" else {
      return nil
    }
    target.dependencies = target.dependencies.filter { dependency in
      if case .byNameItem(name: "_SwiftFormatTestSupport", _) = dependency {
        return true
      }
      return false
    }
    return target
  }
}

let package = Package(
  name: "swift-format",
  platforms: [
    .macOS("13.0"),
    .iOS("16.0"),
  ],
  products: products,
  dependencies: dependencies,
  targets: targets
)

func swiftSyntaxDependencies(_ names: [String]) -> [Target.Dependency] {
  if buildDynamicSwiftSyntaxLibrary {
    return [.product(name: "_SwiftSyntaxDynamic", package: "swift-syntax")]
  } else {
    return names.map { .product(name: $0, package: "swift-syntax") }
  }
}

// MARK: - Parse build arguments

func hasEnvironmentVariable(_ name: String) -> Bool {
  return ProcessInfo.processInfo.environment[name] != nil
}

// When building the toolchain on the CI, don't add the CI's runpath for the
// final build before installing.
var installAction: Bool { hasEnvironmentVariable("SWIFTFORMAT_CI_INSTALL") }

/// Assume that all the package dependencies are checked out next to sourcekit-lsp and use that instead of fetching a
/// remote dependency.
var useLocalDependencies: Bool { hasEnvironmentVariable("SWIFTCI_USE_LOCAL_DEPS") }

/// Build only tests targets and test support modules.
///
/// This is used to test swift-format on Windows, where the modules required for the `swift-format` executable are
/// built using CMake. When using this setting, the caller is responsible for passing the required search paths to
/// the `swift test` invocation so that all pre-built modules can be found.
var buildOnlyTests: Bool { hasEnvironmentVariable("SWIFTFORMAT_BUILD_ONLY_TESTS") }

/// Whether swift-syntax is being built as a single dynamic library instead of as a separate library per module.
///
/// This means that the swift-syntax symbols don't need to be statically linked, which alles us to stay below the
/// maximum number of exported symbols on Windows, in turn allowing us to build sourcekit-lsp using SwiftPM on Windows
/// and run its tests.
var buildDynamicSwiftSyntaxLibrary: Bool {
  hasEnvironmentVariable("SWIFTSYNTAX_BUILD_DYNAMIC_LIBRARY")
}

// MARK: - Dependencies

var dependencies: [Package.Dependency] {
  if buildOnlyTests {
    return []
  } else if useLocalDependencies {
    return [
      .package(path: "../swift-argument-parser"),
      .package(path: "../swift-markdown"),
      .package(path: "../swift-syntax"),
    ]
  } else {
    return [
      .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.2"),
      .package(url: "https://github.com/apple/swift-markdown.git", from: "0.2.0"),
      .package(url: "https://github.com/swiftlang/swift-syntax.git", branch: "main"),
    ]
  }
}

// MARK: - Compute custom build settings

var swiftformatLinkSettings: [LinkerSetting] {
  if installAction {
    return [.unsafeFlags(["-no-toolchain-stdlib-rpath"], .when(platforms: [.linux, .android]))]
  } else {
    return []
  }
}