When building a crate you aren't allowed to write to the source directory. Cargo will refuse to upload a crate to crates.io if you attempt to do this. You are only allowed to touch OUT_DIR and even then you should probably only do this from a build script and not a proc macro.
Would it be possible to instead generate the rust struct from the protobuf specification?
The simpler / faster approach is to have the #[gen_proto] generate hidden functions which are able to generate your desired stuff (usually behind a feature gate). Such functions are collected (either through life-before-main or through linker tricks) into a "compile-time" collection. You then have a special unit test that iterates through that collection and calls all those functions, with some given out path, effectively generating the desired file.
This is the approach currently taken by ::safer_ffi, for instance:
The more proper approach would be to write a CLI kind of application (tip: you could try to use GitHub - trailofbits/dylint: A tool for running Rust lints from dynamic libraries to hook onto lint capabilities to get the code exploration done for you) which traverses your code looking for the attribute / the marker, and have that tool be the one responsible for generating the files. This is what wasm_bindgen does, for instance.
Put another way, if I am running a separate program outside of the Rustc compilation process, I need some way to to extract a TokenStream for everything happening after my proc macro call.