Skip to content

Commit 3b93a31

Browse files
haitakaSpace Team
authored andcommitted
[K/N] Make runtime .bc builds reproducible ^KT-81501
* Make distro build depend on current host, so that runtime modules would not be reused accross different OS builds * Strip user-specific prefixes from absolute paths using `-ffile-preifx-map` * Sort llvm-link inputs Merge-request: KT-MR-24296 Merged-by: Alexey Glushko <[email protected]>
1 parent d05ba36 commit 3b93a31

File tree

6 files changed

+73
-15
lines changed

6 files changed

+73
-15
lines changed

kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/PlatformManagerProvider.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import org.gradle.api.tasks.PathSensitive
1616
import org.gradle.api.tasks.PathSensitivity
1717
import org.gradle.kotlin.dsl.newInstance
1818
import org.jetbrains.kotlin.konan.target.Distribution
19+
import org.jetbrains.kotlin.konan.target.HostManager
1920
import org.jetbrains.kotlin.konan.target.PlatformManager
2021
import org.jetbrains.kotlin.nativeDistribution.asProperties
2122
import org.jetbrains.kotlin.nativeDistribution.llvmDistributionSource
@@ -46,6 +47,16 @@ open class PlatformManagerProvider @Inject constructor(
4647
@get:Input
4748
val konanPropertiesOverride: Map<String, String> = project.llvmDistributionSource.asProperties
4849

50+
/**
51+
* [PlatformManager] may depend on the current host, so the current host must be an input
52+
*
53+
* One example is `llvm`: we do not guarantee that llvm version is the same between Linux and macOS.
54+
* There could be other problems as well.
55+
*/
56+
@get:Input
57+
@Suppress("UNUSED") // used by Gradle via reflection
58+
protected val currentHost = HostManager.host
59+
4960
@get:Input
5061
@get:Optional
5162
protected val konanDataDir = providerFactory.gradleProperty("konan.data.dir")

kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/bitcode/CompileToBitcodePlugin.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import org.jetbrains.kotlin.nativeDistribution.nativeProtoDistribution
3333
import org.jetbrains.kotlin.resolveLlvmUtility
3434
import org.jetbrains.kotlin.testing.native.GoogleTestExtension
3535
import org.jetbrains.kotlin.utils.capitalized
36+
import java.io.File
3637
import java.time.Duration
3738
import javax.inject.Inject
3839

@@ -211,6 +212,12 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) :
211212
private val platformManager = project.extensions.getByType<PlatformManager>()
212213
private val nativeDependencies = project.extensions.getByType<NativeDependenciesExtension>()
213214

215+
private val reproducibilityRootsMap: Map<File, String>
216+
get() = mapOf(
217+
// This is the common root for native dependencies: sysroots, llvm, ...
218+
nativeDependencies.nativeDependenciesRoot to "NATIVE_DEPS",
219+
)
220+
214221
private val allCompilerArgs = module.compilerArgs.map {
215222
it + when (sanitizer) {
216223
null -> emptyList()
@@ -233,6 +240,7 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) :
233240
this.headersDirs.from(this@SourceSet.headersDirs)
234241
this.inputFiles.from(this@SourceSet.inputFiles)
235242
this.workingDirectory.set(module.compilerWorkingDirectory)
243+
this.reproducibilityRootsMap.set(this@SourceSet.reproducibilityRootsMap)
236244
dependsOn(nativeDependencies.llvmDependency)
237245
dependsOn(nativeDependencies.targetDependency(_target))
238246
dependsOn(this@SourceSet.dependencies)
@@ -255,7 +263,7 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) :
255263
from(this@SourceSet.inputFiles.dir)
256264
from(this@SourceSet.headersDirs)
257265
}
258-
arguments.addAll(ClangFrontend.defaultCompilerFlags(headers))
266+
arguments.addAll(ClangFrontend.defaultCompilerFlags(headers, reproducibilityRootsMap))
259267
arguments.addAll(allCompilerArgs)
260268
arguments.addAll(platformManager.clangArgs(target, module.compiler.get()))
261269
output.set(this@SourceSet.outputDirectory.map { it.asFile.absolutePath })
@@ -279,6 +287,7 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) :
279287
this.inputFiles.from(compileTask)
280288
this.outputFile.set(this@SourceSet.outputFile)
281289
this.arguments.set(module.linkerArgs)
290+
this.reproducibilityRootsMap.set(this@SourceSet.reproducibilityRootsMap)
282291
val specs = this@SourceSet.onlyIf
283292
val target = target
284293
onlyIf {

kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/cpp/ClangFrontend.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import org.gradle.api.file.*
1010
import org.gradle.api.internal.file.FileOperations
1111
import org.gradle.api.model.ObjectFactory
1212
import org.gradle.api.provider.ListProperty
13+
import org.gradle.api.provider.MapProperty
1314
import org.gradle.api.provider.Property
1415
import org.gradle.api.tasks.*
1516
import org.gradle.process.ExecOperations
@@ -123,6 +124,9 @@ open class ClangFrontend @Inject constructor(
123124
@get:Internal("Used to compute workUnits and headersPathsRelativeToWorkingDir")
124125
val workingDirectory: DirectoryProperty = objects.directoryProperty()
125126

127+
@get:Internal
128+
val reproducibilityRootsMap: MapProperty<File, String> = objects.mapProperty(File::class.java, String::class.java)
129+
126130
/**
127131
* Locations to search for headers.
128132
*
@@ -165,7 +169,7 @@ open class ClangFrontend @Inject constructor(
165169
inputPathRelativeToWorkingDir.set(workUnit.inputPathRelativeToWorkingDir)
166170
outputFile.set(workUnit.outputFile)
167171
compilerExecutable.set(this@ClangFrontend.compiler)
168-
arguments.set(defaultCompilerFlags(this@ClangFrontend.headersDirs))
172+
arguments.set(defaultCompilerFlags(this@ClangFrontend.headersDirs, this@ClangFrontend.reproducibilityRootsMap.get()))
169173
arguments.addAll(compilerSpecificArgs)
170174
arguments.addAll(this@ClangFrontend.arguments)
171175
this.platformManager.set(platformManager)
@@ -175,10 +179,14 @@ open class ClangFrontend @Inject constructor(
175179
}
176180

177181
companion object {
178-
internal fun defaultCompilerFlags(headersDirs: CppHeadersSet): List<String> = buildList {
182+
internal fun defaultCompilerFlags(headersDirs: CppHeadersSet, reproducibilityRootsMap: Map<File, String>): List<String> = buildList {
179183
add("-c")
180184
add("-emit-llvm")
181185
addAll(headersDirs.asCompilerArguments.get())
186+
// Prevent generated binaries from containing absolute paths
187+
addAll(reproducibilityRootsMap.map {
188+
"-ffile-prefix-map=${it.key}=${it.value}"
189+
})
182190
}
183191
}
184192
}

kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/cpp/LlvmLink.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ import org.gradle.api.file.ConfigurableFileCollection
1010
import org.gradle.api.file.RegularFileProperty
1111
import org.gradle.api.model.ObjectFactory
1212
import org.gradle.api.provider.ListProperty
13+
import org.gradle.api.provider.MapProperty
1314
import org.gradle.api.provider.Property
1415
import org.gradle.api.tasks.CacheableTask
1516
import org.gradle.api.tasks.Input
1617
import org.gradle.api.tasks.InputFiles
18+
import org.gradle.api.tasks.Internal
1719
import org.gradle.api.tasks.Nested
1820
import org.gradle.api.tasks.OutputFile
1921
import org.gradle.api.tasks.PathSensitive
@@ -26,6 +28,8 @@ import org.gradle.workers.WorkerExecutor
2628
import org.jetbrains.kotlin.execLlvmUtility
2729
import org.jetbrains.kotlin.konan.target.PlatformManager
2830
import org.jetbrains.kotlin.platformManagerProvider
31+
import org.jetbrains.kotlin.utils.reproduciblySortedFilePaths
32+
import java.io.File
2933
import javax.inject.Inject
3034

3135
private abstract class LlvmLinkJob : WorkAction<LlvmLinkJob.Parameters> {
@@ -34,6 +38,7 @@ private abstract class LlvmLinkJob : WorkAction<LlvmLinkJob.Parameters> {
3438
val outputFile: RegularFileProperty
3539
val arguments: ListProperty<String>
3640
val platformManager: Property<PlatformManager>
41+
val reproducibilityRootsMap: MapProperty<File, String>
3742
}
3843

3944
@get:Inject
@@ -42,7 +47,8 @@ private abstract class LlvmLinkJob : WorkAction<LlvmLinkJob.Parameters> {
4247
override fun execute() {
4348
with(parameters) {
4449
execOperations.execLlvmUtility(platformManager.get(), "llvm-link") {
45-
args = listOf("-o", outputFile.asFile.get().absolutePath) + arguments.get() + inputFiles.map { it.absolutePath }
50+
val sortedInputs = inputFiles.reproduciblySortedFilePaths(reproducibilityRootsMap.get()).map { it.absolutePath }
51+
args = listOf("-o", outputFile.asFile.get().absolutePath) + arguments.get() + sortedInputs
4652
}
4753
}
4854
}
@@ -75,6 +81,9 @@ open class LlvmLink @Inject constructor(
7581
@get:Input
7682
val arguments: ListProperty<String> = objectFactory.listProperty(String::class.java)
7783

84+
@get:Internal
85+
val reproducibilityRootsMap: MapProperty<File, String> = objectFactory.mapProperty(File::class.java, String::class.java)
86+
7887
@get:Nested
7988
protected val platformManagerProvider = objectFactory.platformManagerProvider(project)
8089

@@ -87,6 +96,7 @@ open class LlvmLink @Inject constructor(
8796
outputFile.set(this@LlvmLink.outputFile)
8897
arguments.set(this@LlvmLink.arguments)
8998
platformManager.set(this@LlvmLink.platformManagerProvider.platformManager)
99+
reproducibilityRootsMap.set(this@LlvmLink.reproducibilityRootsMap)
90100
}
91101
}
92102
}

kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/tools/NativePlugin.kt

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.dependencies.NativeDependenciesExtension
1818
import org.jetbrains.kotlin.dependencies.NativeDependenciesPlugin
1919
import org.jetbrains.kotlin.konan.target.HostManager.Companion.hostIsMac
2020
import org.jetbrains.kotlin.konan.target.HostManager.Companion.hostIsMingw
21+
import org.jetbrains.kotlin.utils.reproduciblySortedFilePaths
2122
import java.io.File
2223
import javax.inject.Inject
2324
import kotlin.collections.List
@@ -232,17 +233,8 @@ open class NativeToolsExtension(val project: Project) {
232233
/**
233234
* Whenever a `FileCollection` is passed as arguments, it's order must be stable sorted for reproducibility.
234235
*/
235-
fun reproduciblySortedFilePaths(fileCollection: FileCollection): List<File> = fileCollection.files.map { file ->
236-
// We cannot just sort absolute paths: they may change from machine to machine.
237-
// So, apply `reproducbilityCompilerFlags` to the list of files manually and use the resulting paths as keys.
238-
reproducibilityRootsMap.firstNotNullOfOrNull { (root, name) ->
239-
if (file.startsWith(root)) {
240-
"$name${File.separator}${file.toRelativeString(root)}" to file
241-
} else {
242-
null
243-
}
244-
} ?: (file.absolutePath to file) // TODO: consider erroring out instead
245-
}.sortedBy { it.first }.map { it.second }
236+
fun reproduciblySortedFilePaths(fileCollection: FileCollection): List<File> =
237+
fileCollection.reproduciblySortedFilePaths(reproducibilityRootsMap)
246238

247239
val sourceSets = SourceSets(project, this, mutableMapOf<String, SourceSet>())
248240
val toolPatterns = ToolConfigurationPatterns(this, mutableMapOf<Pair<String, String>, ToolPatternConfiguration>())
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2010-2025 JetBrains s.r.o. and Kotlin Programming Language contributors.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
4+
*/
5+
6+
package org.jetbrains.kotlin.utils
7+
8+
import org.gradle.api.file.FileCollection
9+
import java.io.File
10+
11+
12+
/**
13+
* Whenever a `FileCollection` is passed as arguments, it's order must be stably sorted for reproducibility.
14+
* @param reproducibilityRootsMap Map of volatile path prefixes to the replacements to be used instead.
15+
*/
16+
fun FileCollection.reproduciblySortedFilePaths(reproducibilityRootsMap: Map<File, String>): List<File> = files.map { file ->
17+
// We cannot just sort absolute paths: they may change from machine to machine.
18+
// So, apply `reproducibilityCompilerFlags` to the list of files manually and use the resulting paths as keys.
19+
reproducibilityRootsMap.firstNotNullOfOrNull { (root, name) ->
20+
if (file.startsWith(root)) {
21+
"$name${File.separator}${file.toRelativeString(root)}" to file
22+
} else if (file.isAbsolute) {
23+
file.path to file
24+
} else {
25+
null
26+
}
27+
} ?: (file.absolutePath to file) // TODO: consider erroring out instead
28+
}.sortedBy { it.first }.map { it.second }

0 commit comments

Comments
 (0)