Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions Sources/SkipBuild/Commands/AndroidCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -375,14 +375,11 @@ fileprivate extension AndroidOperationCommand {
]
env = env.filter({ (key, value) in
permittedEnvironment.contains(key)
&& !key.hasPrefix("SKIP_")
&& !key.hasPrefix("ANDROID_") // "ANDROID_HOME", "ANDROID_NDK_HOME", "ANDROID_NDK_ROOT", "ANDROID_NDK", "ANDROID_SERIAL"
|| key.hasPrefix("SKIP_") // "SKIP_COMMAND_OVERRIDE"
|| key.hasPrefix("ANDROID_") // "ANDROID_HOME", "ANDROID_NDK_HOME", "ANDROID_NDK_ROOT", "ANDROID_NDK", "ANDROID_SERIAL"
})
}

// manually disable the skipstone plugin from being run again in the derived build; we don't need to transpile and bridge the code a second time, we only need to build the native libraries with the Android toolchain
//env["SKIP_PLUGIN_DISABLED"] = "1"

let swiftCmd = toolchainBin.appendingPathComponent("swift", isDirectory: false).path
if !FileManager.default.fileExists(atPath: swiftCmd) {
throw CrossCompilerError(errorDescription: "Could not locate swift command at: \(swiftCmd)")
Expand Down
6 changes: 1 addition & 5 deletions Sources/SkipBuild/Commands/TranspileCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ struct TranspileCommand: TranspilePhase, StreamingCommand {
try fs.createSymbolicLink(addOutputFile(androidTestOutputFolder), pointingAt: outputFolderPath, relative: true)
}

let packageName = KotlinTranslator.packageName(forModule: primaryModuleName)
let packageName = baseSkipConfig.skip?.package ?? KotlinTranslator.packageName(forModule: primaryModuleName)

let transformers: [KotlinTransformer] = try createTransformers(for: baseSkipConfig, with: configMap)

Expand Down Expand Up @@ -1270,10 +1270,6 @@ struct TranspileCommand: TranspilePhase, StreamingCommand {
transformers.append(KotlinDynamicObjectTransformer(root: root))
}

//if let packageName = config.skip?.package {
// TODO: throw error("implement package/module map plugin")
//}

return transformers
}

Expand Down
11 changes: 10 additions & 1 deletion Sources/SkipSyntax/CodebaseInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1389,6 +1389,7 @@ public final class CodebaseInfo {

public final class ModuleExport: Codable {
public let moduleName: String?
public let packageName: String?

// Default visibility for testing
var rootTypes: [TypeInfo] = []
Expand All @@ -1402,11 +1403,19 @@ public final class CodebaseInfo {
private var isPrepared = false

private enum CodingKeys: String, CodingKey {
case moduleName = "m", rootTypes = "t", rootTypealiases = "a", rootVariables = "v", rootFunctions = "f", rootExtensions = "e", sourceFileTable = "stable"
case moduleName = "m"
case packageName = "p"
case rootTypes = "t"
case rootTypealiases = "a"
case rootVariables = "v"
case rootFunctions = "f"
case rootExtensions = "e"
case sourceFileTable = "stable"
}

public init(of codebaseInfo: CodebaseInfo) {
self.moduleName = codebaseInfo.moduleName
self.packageName = codebaseInfo.kotlin?.packageName

// We want to always produce the same encoded output for the same input, because new output from one module might be a signal
// that modules depending on it have to re-transpile. Sort for stability. API within a file will always have been added in the
Expand Down
11 changes: 11 additions & 0 deletions Sources/SkipSyntax/Kotlin/KotlinCodebaseInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,17 @@ public final class KotlinCodebaseInfo: CodebaseInfoLanguageAdditions, CodebaseIn
}

func prepareForUse(codebaseInfo: CodebaseInfo) {
// Populate package name overrides from the current module and dependent modules
KotlinTranslator.packageNameOverrides = [:]
if let currentPackage = packageName, let currentModule = codebaseInfo.moduleName {
KotlinTranslator.packageNameOverrides[currentModule] = currentPackage
}
for moduleExport in codebaseInfo.dependentModules {
if let moduleName = moduleExport.moduleName, let customPackage = moduleExport.packageName {
KotlinTranslator.packageNameOverrides[moduleName] = customPackage
}
}

for moduleExport in codebaseInfo.dependentModules {
// SkipLib conflicts are special cased so that we don't constantly add additional unused imports for Array, etc.
// See KotlinIdentifier, KotlinTypeSignature
Expand Down
13 changes: 13 additions & 0 deletions Sources/SkipSyntax/Kotlin/KotlinTranslator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ public final class KotlinTranslator {
private(set) var codebaseInfo: CodebaseInfo.Context?
private(set) var packageName: String?

/// Module name to custom package name overrides, populated from skip.yml config and dependent module exports.
public static var packageNameOverrides: [String: String] = [:]

public init(syntaxTree: SyntaxTree) {
self.syntaxTree = syntaxTree
}
Expand All @@ -19,9 +22,19 @@ public final class KotlinTranslator {
/// - moduleName: The module name to convert.
/// - Returns: The dot-separated package name.
public static func packageName(forModule moduleName: String, withDefaultPackageSuffix: String? = "module", trimTests: Bool = true) -> String {
// Check for custom package name override
if let override = packageNameOverrides[moduleName] {
return override
}

// Map from e.g. Foundation to SkipFoundation
let moduleName = CodebaseInfo.moduleNameMap[moduleName]?.first ?? moduleName

// Check the mapped module name as well
if let override = packageNameOverrides[moduleName] {
return override
}

// Turn into package name
var lastLower = false
var packageName = ""
Expand Down
4 changes: 2 additions & 2 deletions Tests/SkipSyntaxTests/CodebaseInfoTests.swift

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions Tests/SkipSyntaxTests/KotlinCodebaseInfoTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,41 @@ final class KotlinCodebaseInfoTests: XCTestCase {
XCTAssertEqual(true, context.isKotlinUnconstrainedInterfaceMember(name: "baseProtocolVar", parameters: nil, isStatic: false, in: .named("TestsProtocolImpl", [])))
XCTAssertEqual(true, context.isKotlinUnconstrainedInterfaceMember(name: "baseProtocolFunc", parameters: functionParameters, isStatic: false, in: .named("TestsProtocolImpl", [])))
}

func testPackageNameOverrides() throws {
defer { KotlinTranslator.packageNameOverrides = [:] }

// Set up a dependent module with a custom package name
let depSwift = "public class DepClass { }"
let depFile = try tmpFile(named: "Source_DepModule.swift", contents: depSwift)
let depSource = Source(file: Source.FilePath(path: depFile.path), content: depSwift)
let depTree = SyntaxTree(source: depSource)
let depInfo = CodebaseInfo(moduleName: "DepModule")
depInfo.kotlin = KotlinCodebaseInfo(packageName: "com.example.dep")
depInfo.gather(from: depTree)
depInfo.prepareForUse()
let depExport = CodebaseInfo.ModuleExport(of: depInfo)

// Verify the export captured the custom package name
XCTAssertEqual("com.example.dep", depExport.packageName)

// Set up the main module with a custom package name and the dependent module
let codebaseInfo = CodebaseInfo(moduleName: "MainModule")
codebaseInfo.kotlin = KotlinCodebaseInfo(packageName: "com.example.main")
codebaseInfo.dependentModules = [depExport]
codebaseInfo.prepareForUse()

// Verify overrides are populated for both current and dependent modules
XCTAssertEqual("com.example.main", KotlinTranslator.packageNameOverrides["MainModule"])
XCTAssertEqual("com.example.dep", KotlinTranslator.packageNameOverrides["DepModule"])

// Verify packageName(forModule:) uses overrides
XCTAssertEqual("com.example.main", KotlinTranslator.packageName(forModule: "MainModule"))
XCTAssertEqual("com.example.dep", KotlinTranslator.packageName(forModule: "DepModule"))

// Verify non-overridden modules still use algorithmic names
XCTAssertEqual("other.module", KotlinTranslator.packageName(forModule: "OtherModule"))
}
}

private let swift = """
Expand Down
37 changes: 36 additions & 1 deletion Tests/SkipSyntaxTests/NamingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,46 @@ final class NamingTests: XCTestCase {
""")
}

private func codebaseInfo(moduleName: String, swift: String) throws -> CodebaseInfo {
func testCustomPackageNaming() throws {
KotlinTranslator.packageNameOverrides = ["MyLib": "com.example.mylib"]
defer { KotlinTranslator.packageNameOverrides = [:] }

// Override should be used
XCTAssertEqual("com.example.mylib", KotlinTranslator.packageName(forModule: "MyLib"))
// Non-overridden modules should still use the algorithmic name
XCTAssertEqual("other.module", KotlinTranslator.packageName(forModule: "OtherModule"))
}

func testCustomPackageNameDependentModule() async throws {
let moduleOne = try CodebaseInfo.ModuleExport(of: codebaseInfo(moduleName: "ModuleOne", packageName: "com.custom.one", swift: """
public class A {
public func f() -> Swift.Int {
return 0
}
}
"""))

try await check(dependentModules: [moduleOne], swift: """
import ModuleOne

func f(obj: ModuleOne.A) -> Int {
return obj.f()
}
""", kotlin: """
import com.custom.one.*

internal fun f(obj: com.custom.one.A): Int = obj.f()
""")
}

private func codebaseInfo(moduleName: String, packageName: String? = nil, swift: String) throws -> CodebaseInfo {
let srcFile = try tmpFile(named: "Source_\(moduleName).swift", contents: swift)
let source = Source(file: Source.FilePath(path: srcFile.path), content: swift)
let syntaxTree = SyntaxTree(source: source)
let codebaseInfo = CodebaseInfo(moduleName: moduleName)
if let packageName {
codebaseInfo.kotlin = KotlinCodebaseInfo(packageName: packageName)
}
codebaseInfo.gather(from: syntaxTree)
codebaseInfo.prepareForUse()
return codebaseInfo
Expand Down