-
Notifications
You must be signed in to change notification settings - Fork 15.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
Overcome the ref struct
limitation for pre-roslyn compilers by introducing GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE for generated code
#7490
Conversation
For now I've manually tested the generated code with |
Looking at the code, I think we can do better in terms of avoiding duplication, by being sneaky with a sort of "bait and switch" approach. Basically we're including the same merging body, but in one method or another based on the compatibility. We can do that while only providing that merging code once. Current (pre-PR) code: [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public void MergeFrom(pb::CodedInputStream input) {
input.ReadRawMessage(this);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
MERGING CODE HERE
} Code in this PR: [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public void MergeFrom(pb::CodedInputStream input) {
#if !GOOGLE_PROTOBUF_COMPATIBILITY_MODE
input.ReadRawMessage(this);
#else
MERGING CODE HERE
#endif
}
#if !GOOGLE_PROTOBUF_COMPATIBILITY_MODE
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
MERGING CODE HERE
}
#endif How about instead, we do: [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public void MergeFrom(pb::CodedInputStream input) {
#if !GOOGLE_PROTOBUF_COMPATIBILITY_MODE
input.ReadRawMessage(this);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
#endif
MERGING CODE HERE
} In other words:
This assumes that the merging code is identical between compatibility and non-compatibility mode - that assumption may not be accurate. |
Slightly less ugly alternative (a little duplication, but probably clearer): #if GOOGLE_PROTOBUF_COMPATIBILITY_MODE
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public void MergeFrom(pb::CodedInputStream input) {
#else
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public void MergeFrom(pb::CodedInputStream input) {
input.ReadRawMessage(this);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
#endif
MERGING CODE HERE
} |
That's a good point, unfortunately the merging code is very similar but now quite the same. For repeated field, map field, wrappers and for unknown fields and extensions, we need to pass |
Rats, that's what I get for not looking at a large enough sample. Thanks for the rapid evaluation :) |
Alternatively, the define could be named |
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.
Agree we should consider various options for the #define
symbol name.
But generally, I'm in favour of this. It's a bit of a pity that those with older compilers will need to take action in order to compiler again (we need really good documentation on this!) but it's probably the best balance.
In terms of testing, we should probably be able to add something to the Windows build to use the C# 5 compiler in c:\Windows\Microsoft.NET\Framework\v4.0.30319
to try to build Google.Protobuf.Test.TestProtos. If it can build that, it should be able to build any generated code.
} | ||
|
||
void MapFieldGenerator::GenerateParsingCode(io::Printer* printer, bool use_parse_context) { | ||
if (use_parse_context) { |
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.
Possibly use a conditional operator here instead?
printer->Print(
variables_,
use_parse_context
? "$name$_.AddEntriesFrom(ref input, _map_$name$_codec);\n"
: "$name$_.AddEntriesFrom(input, _map_$name$_codec);\n");
That has the benefit that the two versions are on consecutive lines, making the difference obvious.
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.
(There are a few others, too.)
This seems like a good solution. The cost to anyone working with modern .NET Core is bigger generated code files, but the unused implementation will be removed via |
Some really nice problem solving here! My main feedback is:
|
I believe we should be able to write a test for this, only on Windows. I'm happy to try to do that as a separate PR when this is merged. Basically it would try to build the test proto project using the old native C# compiler. For documentation: I think a combination of release notes and https://developers.google.com/protocol-buffers/docs/csharptutorial is likely to be enough. |
ebf4788
to
c5a7f0f
Compare
c5a7f0f
to
6a92d59
Compare
ref struct
limitation for pre-roslyn compilers.ref struct
limitation for pre-roslyn compilers by introducing GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE for generated code
Ok, I believe I addressed all the feedback and this is now ready for final review.
|
{ | ||
using (var process = new Process()) | ||
{ | ||
process.StartInfo.FileName = "c:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\csc.exe"; // Old C# 5 compiler from .NET framework |
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.
Hard coded path is very brittle. Windows could be installed to a different path or drive.
Should look this up from an environmental variable.
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.
https://stackoverflow.com/questions/6660512/how-to-get-csc-exe-path
I think this would work if the test only runs with the net45 target:
using System.Runtime.InteropServices;
var frameworkPath = RuntimeEnvironment.GetRuntimeDirectory();
var cscPath = Path.Combine(frameworkPath, "csc.exe");
Alternatively the %WINDIR% environment variable would be an improvement. To be more exact and lookup the exact path would probably require accessing Windows registry.
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.
Good point. I updated the test to use the WINDIR env. It's not 100% but seems good enough for our purposes.
I was fine with it last time I looked; I don't have time to take another careful look at the moment I'm afraid - but if @haberman is happy, that's fine for me. |
This looks great. Could you possibly add something to CHANGES.txt in the "Unreleased Changes" section? I've started doing this so that there is less work to do at release time. |
…MODE "... make generated code compatible with old C# compilers (pre-roslyn compilers from .NET framework and old versions of mono) that do not support ref structs." See also: protocolbuffers/protobuf#7490 https://github.com/protocolbuffers/protobuf/releases/tag/v3.13.0
Out of curiosity, how do you enable GOOGLE_PROTOBUF_COMPATIBILITY_MODE ? did you enable it in the |
It's a compile define. You can enable it in your .csproj files. E.g. a similar example here:
Btw, the correct name of the define is |
Hello was able to successfully compile my generated code using #define GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE |
@lirivrod: Please file a new issue with more details - that's more appropriate than using a merged PR. The expectation isn't that you define the symbol in individual files though, but instead do it for the whole project. |
Followup for #7351.
Attempts to fix the issue mentioned in #7351 (comment)
Introduces the
GOOGLE_PROTOBUF_COMPATIBILITY_MODE
define which allows to avoid referencing theParseContext
ref struct in the generated code.The consequences are basically:
ParseFrom(ReadOnlySequence<byte> data)
, because that one requires that the messages implementIBufferMessage
( = support parsing from ParseContext).ref struct
directly (which they generally don't need to do). E.g. someone with an old compiler can still use theParseFrom(ReadOnlySequence<byte> data)
method as long as the generated code was compiled using a newer compiler in advance. That is a good news for the client libraries.The differences compared to the old approach #5888:
GOOGLE_PROTOBUF_COMPATIBILITY_MODE
define to be able to compile the regenerated code.