New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[K/N] Expose program name in runtime #5281
base: master
Are you sure you want to change the base?
Conversation
*^[KTI-54606](https://youtrack.jetbrains.com/issue/KT-54606) Fixed*
@@ -380,6 +380,10 @@ standaloneTest("throw_from_except_constr") { | |||
source = "runtime/exceptions/throw_from_except_constr.kt" | |||
} | |||
|
|||
standaloneTest("program_name") { | |||
source = "runtime/program_name/runtime_program_name.kt" | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file represents the legacy test infrastructure that we are actively trying to get rid of.
Please create a proper test class in
https://github.com/JetBrains/kotlin/tree/master/native/native.tests/tests/org/jetbrains/kotlin/konan/test/blackbox instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ported the testcase to the new infrastructure.
@@ -30,6 +30,8 @@ using namespace kotlin; | |||
//--- Setup args --------------------------------------------------------------// | |||
|
|||
OBJ_GETTER(setupArgs, int argc, const char** argv) { | |||
kotlin::programName = argv[0]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is argv[0]
guaranteed to live long enough? What will happen if some code accesses kotlin::programName
after the main
function finishes?
Is argv
guaranteed to always have at least one element?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- programName will now be set to null before argv leaves scope. I could not find a definite answer if it would be guaranteed to live long enough, so I opted for the safe choice.
- Theoretically it is guaranteed by the POSIX standard. However if it has 0 arguments, the code 1 line below would have crashed. I also added a testcase for argc=0, and added support for it via
std::max(0, ...)
. So now kotlin executables don't crash on startup when they are launched in a non posix compatible way.
* Representation of the name used to invoke the program executable. | ||
*/ | ||
public val programName: String | ||
get() = Platform_getProgramName() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What will happen if the execution is never reached setupArgs
, but this property is accessed? E.g. when compiling Kotlin code to a native library.
A test for that would also be nice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the pointer with the native library. In that case the programName doesn't even have a meaning, therefore it is now nullable. I added a test for that too using the new test infrastructure.
val programFileName = Platform.programName.substringAfterLast("/").substringBeforeLast(".") | ||
|
||
assertEquals("program_name", programFileName) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More tests are necessary. For example, the original issue mentions a nice use case:
Build multi-call binaries. For example, busybox is a single binary that contains many tools, and decides which tool to run based on how the symbolic link is called: https://busybox.net/downloads/BusyBox.html#usage (that's how it can be so small)
So checking a case with symbolic link would also be useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added test for that by calling execv()
in the C code. This allowed to test even more use-cases (no programName at all), while still covering the use-case of renaming/linking a binary.
# Conflicts: # kotlin-native/backend.native/tests/build.gradle
native/native.tests/build.gradle.kts
Outdated
@@ -51,6 +51,7 @@ val stdlibK2Test = nativeTest("stdlibK2Test", "stdlib & frontend-fir") | |||
val kotlinTestLibraryTest = nativeTest("kotlinTestLibraryTest", "kotlin-test & !frontend-fir") | |||
val kotlinTestLibraryK2Test = nativeTest("kotlinTestLibraryK2Test", "kotlin-test & frontend-fir") | |||
val partialLinkageTest = nativeTest("partialLinkageTest", "partial-linkage") | |||
val programNameTest = nativeTest("programNameTest", "program-name") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems a bit redudant. I mean, running these tests separately shouldn't happen frequently, so adding a Gradle task is overkill.
|
||
fun main(args: Array<String>) { | ||
// Remove path and extension (.kexe or .exe) | ||
val programFileName = Platform.programName?.substringAfterLast("/")?.substringBeforeLast(".") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test fails on Windows, because it uses \
instead of /
.
val result = runProcess(cExecutable.absolutePath, binaryName, *args) { | ||
timeout = 60.seconds | ||
} | ||
assertEquals("calling exec...\n$expected", result.stdout) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect this code should be fixed to handle \r\n
on Windows.
|
||
fun validate(expected: String, vararg args: String) { | ||
val binaryName = kotlinCompilation.resultingArtifact.executableFile.path | ||
val result = runProcess(cExecutable.absolutePath, binaryName, *args) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fails if the test target is different from the host. You can reproduce this by running this test with -Pkotlin.internal.native.test.target=<target>
Gradle property.
Please use a proper executor through
Line 18 in 8c69667
val Settings.executor: Executor |
// No program name - this would not be POSIX compliant, see https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html: | ||
// "[...] requires a Strictly Conforming POSIX Application to pass at least one argument to the exec function" | ||
// However, we should not crash the Kotlin runtime because of this. | ||
validate("programName: null\nargs:") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On Linux, this fails for me with
java.lang.AssertionError: Expected <calling exec...
programName: null
args:>, actual <calling exec...
programName:
args:>.
Please take a look.
^KTI-54606 Fixed