-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Extract platform-specifics from ENV to Crystal::System::Env and implement for win32 #6333
Extract platform-specifics from ENV to Crystal::System::Env and implement for win32 #6333
Conversation
src/crystal/system/unix/env.cr
Outdated
module Crystal::System::Env | ||
# Sets an environment variable. | ||
def self.set(key : String, value : String) : Nil | ||
raise ArgumentError.new("Key contains null byte") if key.byte_index(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.
key.check_no_bull_byte
and down the diff
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.
That's what I initially thought, too. But check_no_null_byte
loses the information which of the arguments was invalid (it's String contains null byte
instead of Key contains null byte
). Doesn't make much difference, so I'm not sure about it...
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.
Then refactor it to take an argument
src/crystal/system/win32/env.cr
Outdated
System.retry_wstr_buffer do |buffer, small_buf| | ||
length = LibC.GetEnvironmentVariableW(key.to_utf16, buffer, buffer.size) | ||
if 0 < length < buffer.size | ||
break String.from_utf16(buffer[0, length]) |
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 is probably slightly clearer using return
instead of break
.
src/env.cr
Outdated
value | ||
end | ||
|
||
# Returns `true` if the environment variable named *key* exists and `false` | ||
# if it doesn't. | ||
def self.has_key?(key : String) : Bool | ||
!!LibC.getenv(key) | ||
!!Crystal::System::Env.set?(key) |
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.
rename this to has_key?
and remove the !!
@@ -6,4 +6,6 @@ lib LibC | |||
alias WCHAR = UInt16 | |||
alias LPSTR = CHAR* | |||
alias LPWSTR = WCHAR* | |||
alias LPCWSTR = WCHAR* |
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.
LPCWSTR
shouldn't be an alias since it's just constant which means nothing ABI-wise.
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.
So... what should it be?
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.
LPWSTR
...
@@ -5,6 +5,8 @@ require "c/int_safe" | |||
lib LibC | |||
fun GetLastError : DWORD | |||
|
|||
ERROR_ENVVAR_NOT_FOUND = 203_u32 |
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.
ca2905f
to
7efc63e
Compare
52da7cc
to
a68fd42
Compare
Rebased after #5623 was merged and ready for review 👍 |
a68fd42
to
9bf5cb8
Compare
# | ||
# Invalid values are encoded using the unicode replacement char with | ||
# codepoint `0xfffd`. | ||
def self.from_utf16(pointer : Pointer(UInt16)) : {String, Pointer(UInt16)} |
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 is a breaking change and as such needs some more discussion.
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.
Sure. When reading from a pointer it needs to advance the amount of UInt16
read. We can't determine that afterwards from String#bytesize
because UTF-8 and UTF-16 have different bytesizes. We could rename the method with this changed behaviour (to maybe String.from_utf16_advancing_pointer
). Or change the signature in some way. But I don't think it makes much sense to have both a version of this method (with pointer) that returns the advanced pointer and one that doesn't. It's easy to just ignore the returned pointer value if you don't need it.
This is why I recommended keeping the UTF-16 implementation internal until we got it working well in Windows, otherwise we are bound to make breaking changes. I'd say for now don't worry about breaking changes here, I doubt anyone is using these methods, and if they are, well, it's a simple fix. |
I'm fine with this breaking change actually |
Remove LPCWSTR
src/string.cr
Outdated
def check_no_null_byte(name = nil) | ||
if byte_index(0) | ||
name = "`#{name}` " | ||
raise ArgumentError.new("String #{name}contains null byte") |
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.
Why is this on two lines??
Also, I'd rather the entire exception message was passed in, i.e. def check_no_null_byte(message = "String contains null byte")
.
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 agree with the first comment, disagree with the second one.
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.
The only thing you save by doing it the other way is a bit of typing, and the benefit of having fully customized error messages. For "Key contains null byte" vs "String `key` contains null byte" - the former is way better. Please, let's keep it simple, understandable and flexible.
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.
Two lines because I forgot a if name
in the first line. To avoid empty argument to be printed. That's why the specs fail.
Maybe full message is better. But actually, I don't think there is much benefit from it. Yes, it's more flexible, but I don't see any use for that. check_no_null_byte
is only used to validate method arguments and for such it makes sense to declare the name of the argument if there are more than one. But apart from that, I don't see any need for customization.
Refactor String#check_no_null_byte
ec4b780
to
5de7a7a
Compare
raise ArgumentError.new("String contains null byte") if byte_index(0) | ||
def check_no_null_byte(name = nil) | ||
if byte_index(0) | ||
name = "`#{name}` " if 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.
Why the backticks?
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.
To show that this refers to a name. This formatting is used in many other compiler and stdlib error messages.
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.
Weird, I never noticed that.
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 think it's not that common
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.
IMHO Key contains null byte
reads way better than String 'key' contains null byte
.
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.
@asterite Do you mean name
should be required?
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.
Perhaps check_no_null_byte(what = "String")
then just #{what} contains null byte
?
Or just go with making it fully configurable.
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'm looking at the code, it always seems to be a check against an argument. To avoid repeating the argument name, maybe we can have:
class String
macro check_no_null_byte(name)
raise ArgumentError.new("Argument '{{name}}' contains null byte") if {{name}}.byte_index(0)
{{name}}
end
end
Then it can be used like this:
def some_method(arg)
String.check_no_null_byte(arg)
# do something
end
That way there's no string interpolation, and there's no need to repeat the argument 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 is bikeshedding and overengineering. The diff is fine as-is.
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.
@RX14 I was writing the same thing. All are valid options and it doesn't make a difference regarding ENV.
Anyone can send a PR later to refactor check_no_null_byte
if you care enough.
5de7a7a
to
2d01d82
Compare
Use String#check_no_null_byte
2d01d82
to
7adb2cc
Compare
Add spec for empty environment variable. This fails on win32 because `GetEnvironmentVariableW` doesnt differentiate between unset variable and empty string. In contrast, `GetEnvironmentStringsW` contains empty variables.
I discovered an issue on win32: This could be used to identify empty values if Unless someone has a idea how to prevent this, I don't think there is anything to be done. |
@straight-shoota i'm pretty skeptical that |
This stackoverflow answer seems to show the Win32 API correctly returning the value of an empty env var: https://stackoverflow.com/questions/26993642/call-to-environment-getenvironmentvariable-affects-subsequent-calls But it does show there's definitely weirdness around this part of the API - perhaps |
Fix implementation for empty string on win32 using `SetLastError`.
It actually seems to be the case that
I fixed it by adding a call to |
Oh actually, can you uncomment out the file/dir specs which were commented out due to using ENV? |
As far as I can see, they're all pending for other reasons. |
@straight-shoota these should go from macroed out to just pending Line 585 in 8d28cbb
|
66166cb
to
0b33619
Compare
spec/std/file_spec.cr
Outdated
@@ -582,41 +582,38 @@ describe "File" do | |||
File.expand_path("~/a", "/tmp/gumby/ddd").should eq(File.join([home, "a"])) | |||
end | |||
|
|||
# TODO: these specs don't compile on windows because ENV isn't ported | |||
# TODO: remove /\A\/\// hack after this is removed from macros |
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.
please check the diff for #5623 and do this TODO too please
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.
Sorry, I'm not sure what you mean... reinstating %r{\A//}
?
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.
yes, and remove the TODO comment
ping, needs a second review |
@RX14 The commit message is a mess... it should've been squashed without all these fixups. |
Requires #5623 (for win32).