xref: /aosp_15_r20/external/grpc-grpc/src/csharp/Grpc.Tools.Tests/scripts/fakeprotoc.py (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
1*cc02d7e2SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*cc02d7e2SAndroid Build Coastguard Worker# Copyright 2022 The gRPC Authors
3*cc02d7e2SAndroid Build Coastguard Worker#
4*cc02d7e2SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
5*cc02d7e2SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
6*cc02d7e2SAndroid Build Coastguard Worker# You may obtain a copy of the License at
7*cc02d7e2SAndroid Build Coastguard Worker#
8*cc02d7e2SAndroid Build Coastguard Worker#     http://www.apache.org/licenses/LICENSE-2.0
9*cc02d7e2SAndroid Build Coastguard Worker#
10*cc02d7e2SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
11*cc02d7e2SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
12*cc02d7e2SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*cc02d7e2SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
14*cc02d7e2SAndroid Build Coastguard Worker# limitations under the License.
15*cc02d7e2SAndroid Build Coastguard Worker
16*cc02d7e2SAndroid Build Coastguard Worker# Fake protobuf compiler for use in the Grpc.Tools MSBuild integration
17*cc02d7e2SAndroid Build Coastguard Worker# unit tests.  Its purpose is to be called from the Grpc.Tools
18*cc02d7e2SAndroid Build Coastguard Worker# Google.Protobuf.Tools.targets MSBuild file instead of the actual protoc
19*cc02d7e2SAndroid Build Coastguard Worker# compiler. This script:
20*cc02d7e2SAndroid Build Coastguard Worker# - parses the command line arguments
21*cc02d7e2SAndroid Build Coastguard Worker# - generates expected dependencies file
22*cc02d7e2SAndroid Build Coastguard Worker# - generates dummy .cs files that are expected by the tests
23*cc02d7e2SAndroid Build Coastguard Worker# - writes a JSON results file containing the arguments passed in
24*cc02d7e2SAndroid Build Coastguard Worker
25*cc02d7e2SAndroid Build Coastguard Worker# Configuration is done via environment variables as it is not possible
26*cc02d7e2SAndroid Build Coastguard Worker# to pass additional argument when called from the MSBuild scripts under test.
27*cc02d7e2SAndroid Build Coastguard Worker#
28*cc02d7e2SAndroid Build Coastguard Worker# Environment variables:
29*cc02d7e2SAndroid Build Coastguard Worker# FAKEPROTOC_PROJECTDIR - project directory
30*cc02d7e2SAndroid Build Coastguard Worker# FAKEPROTOC_OUTDIR - output directory for generated files and output file
31*cc02d7e2SAndroid Build Coastguard Worker# FAKEPROTOC_GENERATE_EXPECTED - list of expected generated files in format:
32*cc02d7e2SAndroid Build Coastguard Worker#         file1.proto:csfile1.cs;csfile2.cs|file2.proto:csfile3.cs;csfile4.cs|...
33*cc02d7e2SAndroid Build Coastguard Worker
34*cc02d7e2SAndroid Build Coastguard Workerimport datetime
35*cc02d7e2SAndroid Build Coastguard Workerimport hashlib
36*cc02d7e2SAndroid Build Coastguard Workerimport json
37*cc02d7e2SAndroid Build Coastguard Workerimport os
38*cc02d7e2SAndroid Build Coastguard Workerimport sys
39*cc02d7e2SAndroid Build Coastguard Worker
40*cc02d7e2SAndroid Build Coastguard Worker# Set to True to write out debug messages from this script
41*cc02d7e2SAndroid Build Coastguard Worker_dbg = True
42*cc02d7e2SAndroid Build Coastguard Worker# file to which write the debug log
43*cc02d7e2SAndroid Build Coastguard Worker_dbgfile = None
44*cc02d7e2SAndroid Build Coastguard Worker
45*cc02d7e2SAndroid Build Coastguard Worker
46*cc02d7e2SAndroid Build Coastguard Workerdef _open_debug_log(filename):
47*cc02d7e2SAndroid Build Coastguard Worker    """Create debug file for this script."""
48*cc02d7e2SAndroid Build Coastguard Worker    global _dbgfile
49*cc02d7e2SAndroid Build Coastguard Worker    if _dbg:
50*cc02d7e2SAndroid Build Coastguard Worker        # append mode since this script may be called multiple times
51*cc02d7e2SAndroid Build Coastguard Worker        # during one build/test
52*cc02d7e2SAndroid Build Coastguard Worker        _dbgfile = open(filename, "a")
53*cc02d7e2SAndroid Build Coastguard Worker
54*cc02d7e2SAndroid Build Coastguard Worker
55*cc02d7e2SAndroid Build Coastguard Workerdef _close_debug_log():
56*cc02d7e2SAndroid Build Coastguard Worker    """Close the debug log file."""
57*cc02d7e2SAndroid Build Coastguard Worker    if _dbgfile:
58*cc02d7e2SAndroid Build Coastguard Worker        _dbgfile.close()
59*cc02d7e2SAndroid Build Coastguard Worker
60*cc02d7e2SAndroid Build Coastguard Worker
61*cc02d7e2SAndroid Build Coastguard Workerdef _write_debug(msg):
62*cc02d7e2SAndroid Build Coastguard Worker    """Write to the debug log file if debug is enabled."""
63*cc02d7e2SAndroid Build Coastguard Worker    if _dbg and _dbgfile:
64*cc02d7e2SAndroid Build Coastguard Worker        print(msg, file=_dbgfile, flush=True)
65*cc02d7e2SAndroid Build Coastguard Worker
66*cc02d7e2SAndroid Build Coastguard Worker
67*cc02d7e2SAndroid Build Coastguard Workerdef _read_protoc_arguments():
68*cc02d7e2SAndroid Build Coastguard Worker    """
69*cc02d7e2SAndroid Build Coastguard Worker    Get the protoc argument from the command line and
70*cc02d7e2SAndroid Build Coastguard Worker    any response files specified on the command line.
71*cc02d7e2SAndroid Build Coastguard Worker
72*cc02d7e2SAndroid Build Coastguard Worker    Returns the list of arguments.
73*cc02d7e2SAndroid Build Coastguard Worker    """
74*cc02d7e2SAndroid Build Coastguard Worker    _write_debug("\nread_protoc_arguments")
75*cc02d7e2SAndroid Build Coastguard Worker    result = []
76*cc02d7e2SAndroid Build Coastguard Worker    for arg in sys.argv[1:]:
77*cc02d7e2SAndroid Build Coastguard Worker        _write_debug("  arg: " + arg)
78*cc02d7e2SAndroid Build Coastguard Worker        if arg.startswith("@"):
79*cc02d7e2SAndroid Build Coastguard Worker            # TODO(jtattermusch): inserting a "commented out" argument feels hacky
80*cc02d7e2SAndroid Build Coastguard Worker            result.append("# RSP file: %s" % arg)
81*cc02d7e2SAndroid Build Coastguard Worker            rsp_file_name = arg[1:]
82*cc02d7e2SAndroid Build Coastguard Worker            result.extend(_read_rsp_file(rsp_file_name))
83*cc02d7e2SAndroid Build Coastguard Worker        else:
84*cc02d7e2SAndroid Build Coastguard Worker            result.append(arg)
85*cc02d7e2SAndroid Build Coastguard Worker    return result
86*cc02d7e2SAndroid Build Coastguard Worker
87*cc02d7e2SAndroid Build Coastguard Worker
88*cc02d7e2SAndroid Build Coastguard Workerdef _read_rsp_file(rspfile):
89*cc02d7e2SAndroid Build Coastguard Worker    """
90*cc02d7e2SAndroid Build Coastguard Worker    Returns list of arguments from a response file.
91*cc02d7e2SAndroid Build Coastguard Worker    """
92*cc02d7e2SAndroid Build Coastguard Worker    _write_debug("\nread_rsp_file: " + rspfile)
93*cc02d7e2SAndroid Build Coastguard Worker    result = []
94*cc02d7e2SAndroid Build Coastguard Worker    with open(rspfile, "r") as rsp:
95*cc02d7e2SAndroid Build Coastguard Worker        for line in rsp:
96*cc02d7e2SAndroid Build Coastguard Worker            line = line.strip()
97*cc02d7e2SAndroid Build Coastguard Worker            _write_debug("    line: " + line)
98*cc02d7e2SAndroid Build Coastguard Worker            result.append(line)
99*cc02d7e2SAndroid Build Coastguard Worker    return result
100*cc02d7e2SAndroid Build Coastguard Worker
101*cc02d7e2SAndroid Build Coastguard Worker
102*cc02d7e2SAndroid Build Coastguard Workerdef _parse_protoc_arguments(protoc_args, projectdir):
103*cc02d7e2SAndroid Build Coastguard Worker    """
104*cc02d7e2SAndroid Build Coastguard Worker    Parse the protoc arguments from the provided list
105*cc02d7e2SAndroid Build Coastguard Worker    """
106*cc02d7e2SAndroid Build Coastguard Worker
107*cc02d7e2SAndroid Build Coastguard Worker    _write_debug("\nparse_protoc_arguments")
108*cc02d7e2SAndroid Build Coastguard Worker    arg_dict = {}
109*cc02d7e2SAndroid Build Coastguard Worker    for arg in protoc_args:
110*cc02d7e2SAndroid Build Coastguard Worker        _write_debug("Parsing: %s" % arg)
111*cc02d7e2SAndroid Build Coastguard Worker
112*cc02d7e2SAndroid Build Coastguard Worker        # All arguments containing file or directory paths are
113*cc02d7e2SAndroid Build Coastguard Worker        # normalized by converting all '\' and changed to '/'
114*cc02d7e2SAndroid Build Coastguard Worker        if arg.startswith("--"):
115*cc02d7e2SAndroid Build Coastguard Worker            # Assumes that cmdline arguments are always passed in the
116*cc02d7e2SAndroid Build Coastguard Worker            # "--somearg=argvalue", which happens to be the form that
117*cc02d7e2SAndroid Build Coastguard Worker            # msbuild integration uses, but it's not the only way.
118*cc02d7e2SAndroid Build Coastguard Worker            (name, value) = arg.split("=", 1)
119*cc02d7e2SAndroid Build Coastguard Worker
120*cc02d7e2SAndroid Build Coastguard Worker            if (
121*cc02d7e2SAndroid Build Coastguard Worker                name == "--dependency_out"
122*cc02d7e2SAndroid Build Coastguard Worker                or name == "--grpc_out"
123*cc02d7e2SAndroid Build Coastguard Worker                or name == "--csharp_out"
124*cc02d7e2SAndroid Build Coastguard Worker            ):
125*cc02d7e2SAndroid Build Coastguard Worker                # For args that contain a path, make the path absolute and normalize it
126*cc02d7e2SAndroid Build Coastguard Worker                # to make it easier to assert equality in tests.
127*cc02d7e2SAndroid Build Coastguard Worker                value = _normalized_absolute_path(value)
128*cc02d7e2SAndroid Build Coastguard Worker
129*cc02d7e2SAndroid Build Coastguard Worker            if name == "--proto_path":
130*cc02d7e2SAndroid Build Coastguard Worker                # for simplicity keep this one as relative path rather than absolute path
131*cc02d7e2SAndroid Build Coastguard Worker                # since it is an input file that is always be near the project file
132*cc02d7e2SAndroid Build Coastguard Worker                value = _normalized_relative_to_projectdir(value, projectdir)
133*cc02d7e2SAndroid Build Coastguard Worker
134*cc02d7e2SAndroid Build Coastguard Worker            _add_protoc_arg_to_dict(arg_dict, name, value)
135*cc02d7e2SAndroid Build Coastguard Worker
136*cc02d7e2SAndroid Build Coastguard Worker        elif arg.startswith("#"):
137*cc02d7e2SAndroid Build Coastguard Worker            pass  # ignore
138*cc02d7e2SAndroid Build Coastguard Worker        else:
139*cc02d7e2SAndroid Build Coastguard Worker            # arg represents a proto file name
140*cc02d7e2SAndroid Build Coastguard Worker            arg = _normalized_relative_to_projectdir(arg, projectdir)
141*cc02d7e2SAndroid Build Coastguard Worker            _add_protoc_arg_to_dict(arg_dict, "protofile", arg)
142*cc02d7e2SAndroid Build Coastguard Worker    return arg_dict
143*cc02d7e2SAndroid Build Coastguard Worker
144*cc02d7e2SAndroid Build Coastguard Worker
145*cc02d7e2SAndroid Build Coastguard Workerdef _add_protoc_arg_to_dict(arg_dict, name, value):
146*cc02d7e2SAndroid Build Coastguard Worker    """
147*cc02d7e2SAndroid Build Coastguard Worker    Add the arguments with name/value to a multi-dictionary of arguments
148*cc02d7e2SAndroid Build Coastguard Worker    """
149*cc02d7e2SAndroid Build Coastguard Worker    if name not in arg_dict:
150*cc02d7e2SAndroid Build Coastguard Worker        arg_dict[name] = []
151*cc02d7e2SAndroid Build Coastguard Worker
152*cc02d7e2SAndroid Build Coastguard Worker    arg_dict[name].append(value)
153*cc02d7e2SAndroid Build Coastguard Worker
154*cc02d7e2SAndroid Build Coastguard Worker
155*cc02d7e2SAndroid Build Coastguard Workerdef _normalized_relative_to_projectdir(file, projectdir):
156*cc02d7e2SAndroid Build Coastguard Worker    """Convert a file path to one relative to the project directory."""
157*cc02d7e2SAndroid Build Coastguard Worker    try:
158*cc02d7e2SAndroid Build Coastguard Worker        return _normalize_slashes(
159*cc02d7e2SAndroid Build Coastguard Worker            os.path.relpath(os.path.abspath(file), projectdir)
160*cc02d7e2SAndroid Build Coastguard Worker        )
161*cc02d7e2SAndroid Build Coastguard Worker    except ValueError:
162*cc02d7e2SAndroid Build Coastguard Worker        # On Windows if the paths are on different drives then we get this error
163*cc02d7e2SAndroid Build Coastguard Worker        # Just return the absolute path
164*cc02d7e2SAndroid Build Coastguard Worker        return _normalize_slashes(os.path.abspath(file))
165*cc02d7e2SAndroid Build Coastguard Worker
166*cc02d7e2SAndroid Build Coastguard Worker
167*cc02d7e2SAndroid Build Coastguard Workerdef _normalized_absolute_path(file):
168*cc02d7e2SAndroid Build Coastguard Worker    """Returns normalized absolute path to file."""
169*cc02d7e2SAndroid Build Coastguard Worker    return _normalize_slashes(os.path.abspath(file))
170*cc02d7e2SAndroid Build Coastguard Worker
171*cc02d7e2SAndroid Build Coastguard Worker
172*cc02d7e2SAndroid Build Coastguard Workerdef _normalize_slashes(path):
173*cc02d7e2SAndroid Build Coastguard Worker    """Change all backslashes to forward slashes to make comparing path strings easier."""
174*cc02d7e2SAndroid Build Coastguard Worker    return path.replace("\\", "/")
175*cc02d7e2SAndroid Build Coastguard Worker
176*cc02d7e2SAndroid Build Coastguard Worker
177*cc02d7e2SAndroid Build Coastguard Workerdef _write_or_update_results_json(log_dir, protofile, protoc_arg_dict):
178*cc02d7e2SAndroid Build Coastguard Worker    """Write or update the results JSON file"""
179*cc02d7e2SAndroid Build Coastguard Worker
180*cc02d7e2SAndroid Build Coastguard Worker    # Read existing json.
181*cc02d7e2SAndroid Build Coastguard Worker    # Since protoc may be called more than once each build/test if there is
182*cc02d7e2SAndroid Build Coastguard Worker    # more than one protoc file, we read the existing data to add to it.
183*cc02d7e2SAndroid Build Coastguard Worker    fname = os.path.abspath("%s/results.json" % log_dir)
184*cc02d7e2SAndroid Build Coastguard Worker    if os.path.isfile(fname):
185*cc02d7e2SAndroid Build Coastguard Worker        # Load the original contents.
186*cc02d7e2SAndroid Build Coastguard Worker        with open(fname, "r") as forig:
187*cc02d7e2SAndroid Build Coastguard Worker            results_json = json.load(forig)
188*cc02d7e2SAndroid Build Coastguard Worker    else:
189*cc02d7e2SAndroid Build Coastguard Worker        results_json = {}
190*cc02d7e2SAndroid Build Coastguard Worker        results_json["Files"] = {}
191*cc02d7e2SAndroid Build Coastguard Worker
192*cc02d7e2SAndroid Build Coastguard Worker    results_json["Files"][protofile] = protoc_arg_dict
193*cc02d7e2SAndroid Build Coastguard Worker    results_json["Metadata"] = {"timestamp": str(datetime.datetime.now())}
194*cc02d7e2SAndroid Build Coastguard Worker
195*cc02d7e2SAndroid Build Coastguard Worker    with open(fname, "w") as fout:
196*cc02d7e2SAndroid Build Coastguard Worker        json.dump(results_json, fout, indent=4)
197*cc02d7e2SAndroid Build Coastguard Worker
198*cc02d7e2SAndroid Build Coastguard Worker
199*cc02d7e2SAndroid Build Coastguard Workerdef _parse_generate_expected(generate_expected_str):
200*cc02d7e2SAndroid Build Coastguard Worker    """
201*cc02d7e2SAndroid Build Coastguard Worker    Parse FAKEPROTOC_GENERATE_EXPECTED that specifies the proto files
202*cc02d7e2SAndroid Build Coastguard Worker    and the cs files to generate. We rely on the test to say what is
203*cc02d7e2SAndroid Build Coastguard Worker    expected rather than trying to work it out in this script.
204*cc02d7e2SAndroid Build Coastguard Worker
205*cc02d7e2SAndroid Build Coastguard Worker    The format of the input is:
206*cc02d7e2SAndroid Build Coastguard Worker        file1.proto:csfile1.cs;csfile2.cs|file2.proto:csfile3.cs;csfile4.cs|...
207*cc02d7e2SAndroid Build Coastguard Worker    """
208*cc02d7e2SAndroid Build Coastguard Worker    _write_debug("\nparse_generate_expected")
209*cc02d7e2SAndroid Build Coastguard Worker
210*cc02d7e2SAndroid Build Coastguard Worker    result = {}
211*cc02d7e2SAndroid Build Coastguard Worker    entries = generate_expected_str.split("|")
212*cc02d7e2SAndroid Build Coastguard Worker    for entry in entries:
213*cc02d7e2SAndroid Build Coastguard Worker        parts = entry.split(":")
214*cc02d7e2SAndroid Build Coastguard Worker        pfile = _normalize_slashes(parts[0])
215*cc02d7e2SAndroid Build Coastguard Worker        csfiles = parts[1].split(";")
216*cc02d7e2SAndroid Build Coastguard Worker        result[pfile] = csfiles
217*cc02d7e2SAndroid Build Coastguard Worker        _write_debug(pfile + " : " + str(csfiles))
218*cc02d7e2SAndroid Build Coastguard Worker    return result
219*cc02d7e2SAndroid Build Coastguard Worker
220*cc02d7e2SAndroid Build Coastguard Worker
221*cc02d7e2SAndroid Build Coastguard Workerdef _get_cs_files_to_generate(protofile, proto_to_generated):
222*cc02d7e2SAndroid Build Coastguard Worker    """Returns list of .cs files to generated based on FAKEPROTOC_GENERATE_EXPECTED env."""
223*cc02d7e2SAndroid Build Coastguard Worker    protoname_normalized = _normalize_slashes(protofile)
224*cc02d7e2SAndroid Build Coastguard Worker    cs_files_to_generate = proto_to_generated.get(protoname_normalized)
225*cc02d7e2SAndroid Build Coastguard Worker    return cs_files_to_generate
226*cc02d7e2SAndroid Build Coastguard Worker
227*cc02d7e2SAndroid Build Coastguard Worker
228*cc02d7e2SAndroid Build Coastguard Workerdef _is_grpc_out_file(csfile):
229*cc02d7e2SAndroid Build Coastguard Worker    """Return true if the file is one that would be generated by gRPC plugin"""
230*cc02d7e2SAndroid Build Coastguard Worker    # This is using the heuristics of checking that the name of the file
231*cc02d7e2SAndroid Build Coastguard Worker    # matches *Grpc.cs which is the name that the gRPC plugin would produce.
232*cc02d7e2SAndroid Build Coastguard Worker    return csfile.endswith("Grpc.cs")
233*cc02d7e2SAndroid Build Coastguard Worker
234*cc02d7e2SAndroid Build Coastguard Worker
235*cc02d7e2SAndroid Build Coastguard Workerdef _generate_cs_files(
236*cc02d7e2SAndroid Build Coastguard Worker    protofile, cs_files_to_generate, grpc_out_dir, csharp_out_dir, projectdir
237*cc02d7e2SAndroid Build Coastguard Worker):
238*cc02d7e2SAndroid Build Coastguard Worker    """Create expected cs files."""
239*cc02d7e2SAndroid Build Coastguard Worker    _write_debug("\ngenerate_cs_files")
240*cc02d7e2SAndroid Build Coastguard Worker
241*cc02d7e2SAndroid Build Coastguard Worker    if not cs_files_to_generate:
242*cc02d7e2SAndroid Build Coastguard Worker        _write_debug("No .cs files matching proto file name %s" % protofile)
243*cc02d7e2SAndroid Build Coastguard Worker        return
244*cc02d7e2SAndroid Build Coastguard Worker
245*cc02d7e2SAndroid Build Coastguard Worker    if not os.path.isabs(grpc_out_dir):
246*cc02d7e2SAndroid Build Coastguard Worker        # if not absolute, it is relative to project directory
247*cc02d7e2SAndroid Build Coastguard Worker        grpc_out_dir = os.path.abspath("%s/%s" % (projectdir, grpc_out_dir))
248*cc02d7e2SAndroid Build Coastguard Worker
249*cc02d7e2SAndroid Build Coastguard Worker    if not os.path.isabs(csharp_out_dir):
250*cc02d7e2SAndroid Build Coastguard Worker        # if not absolute, it is relative to project directory
251*cc02d7e2SAndroid Build Coastguard Worker        csharp_out_dir = os.path.abspath("%s/%s" % (projectdir, csharp_out_dir))
252*cc02d7e2SAndroid Build Coastguard Worker
253*cc02d7e2SAndroid Build Coastguard Worker    # Ensure directories exist
254*cc02d7e2SAndroid Build Coastguard Worker    if not os.path.isdir(grpc_out_dir):
255*cc02d7e2SAndroid Build Coastguard Worker        os.makedirs(grpc_out_dir)
256*cc02d7e2SAndroid Build Coastguard Worker
257*cc02d7e2SAndroid Build Coastguard Worker    if not os.path.isdir(csharp_out_dir):
258*cc02d7e2SAndroid Build Coastguard Worker        os.makedirs(csharp_out_dir)
259*cc02d7e2SAndroid Build Coastguard Worker
260*cc02d7e2SAndroid Build Coastguard Worker    timestamp = str(datetime.datetime.now())
261*cc02d7e2SAndroid Build Coastguard Worker    for csfile in cs_files_to_generate:
262*cc02d7e2SAndroid Build Coastguard Worker        if csfile.endswith("Grpc.cs"):
263*cc02d7e2SAndroid Build Coastguard Worker            csfile_fullpath = "%s/%s" % (grpc_out_dir, csfile)
264*cc02d7e2SAndroid Build Coastguard Worker        else:
265*cc02d7e2SAndroid Build Coastguard Worker            csfile_fullpath = "%s/%s" % (csharp_out_dir, csfile)
266*cc02d7e2SAndroid Build Coastguard Worker        _write_debug("Creating: %s" % csfile_fullpath)
267*cc02d7e2SAndroid Build Coastguard Worker        with open(csfile_fullpath, "w") as fout:
268*cc02d7e2SAndroid Build Coastguard Worker            print("// Generated by fake protoc: %s" % timestamp, file=fout)
269*cc02d7e2SAndroid Build Coastguard Worker
270*cc02d7e2SAndroid Build Coastguard Worker
271*cc02d7e2SAndroid Build Coastguard Workerdef _create_dependency_file(
272*cc02d7e2SAndroid Build Coastguard Worker    protofile,
273*cc02d7e2SAndroid Build Coastguard Worker    cs_files_to_generate,
274*cc02d7e2SAndroid Build Coastguard Worker    dependencyfile,
275*cc02d7e2SAndroid Build Coastguard Worker    grpc_out_dir,
276*cc02d7e2SAndroid Build Coastguard Worker    csharp_out_dir,
277*cc02d7e2SAndroid Build Coastguard Worker):
278*cc02d7e2SAndroid Build Coastguard Worker    """Create the expected dependency file."""
279*cc02d7e2SAndroid Build Coastguard Worker    _write_debug("\ncreate_dependency_file")
280*cc02d7e2SAndroid Build Coastguard Worker
281*cc02d7e2SAndroid Build Coastguard Worker    if not dependencyfile:
282*cc02d7e2SAndroid Build Coastguard Worker        _write_debug("dependencyfile is not set.")
283*cc02d7e2SAndroid Build Coastguard Worker        return
284*cc02d7e2SAndroid Build Coastguard Worker
285*cc02d7e2SAndroid Build Coastguard Worker    if not cs_files_to_generate:
286*cc02d7e2SAndroid Build Coastguard Worker        _write_debug("No .cs files matching proto file name %s" % protofile)
287*cc02d7e2SAndroid Build Coastguard Worker        return
288*cc02d7e2SAndroid Build Coastguard Worker
289*cc02d7e2SAndroid Build Coastguard Worker    _write_debug("Creating dependency file: %s" % dependencyfile)
290*cc02d7e2SAndroid Build Coastguard Worker    with open(dependencyfile, "w") as out:
291*cc02d7e2SAndroid Build Coastguard Worker        nfiles = len(cs_files_to_generate)
292*cc02d7e2SAndroid Build Coastguard Worker        for i in range(0, nfiles):
293*cc02d7e2SAndroid Build Coastguard Worker            csfile = cs_files_to_generate[i]
294*cc02d7e2SAndroid Build Coastguard Worker            if csfile.endswith("Grpc.cs"):
295*cc02d7e2SAndroid Build Coastguard Worker                cs_filename = os.path.join(grpc_out_dir, csfile)
296*cc02d7e2SAndroid Build Coastguard Worker            else:
297*cc02d7e2SAndroid Build Coastguard Worker                cs_filename = os.path.join(csharp_out_dir, csfile)
298*cc02d7e2SAndroid Build Coastguard Worker            if i == nfiles - 1:
299*cc02d7e2SAndroid Build Coastguard Worker                print("%s: %s" % (cs_filename, protofile), file=out)
300*cc02d7e2SAndroid Build Coastguard Worker            else:
301*cc02d7e2SAndroid Build Coastguard Worker                print("%s \\" % cs_filename, file=out)
302*cc02d7e2SAndroid Build Coastguard Worker
303*cc02d7e2SAndroid Build Coastguard Worker
304*cc02d7e2SAndroid Build Coastguard Workerdef _getenv(name):
305*cc02d7e2SAndroid Build Coastguard Worker    # Note there is a bug in .NET core 3.x that lowercases the environment
306*cc02d7e2SAndroid Build Coastguard Worker    # variable names when they are added via Process.StartInfo, so we need to
307*cc02d7e2SAndroid Build Coastguard Worker    # check both cases here (only an issue on Linux which is case sensitive)
308*cc02d7e2SAndroid Build Coastguard Worker    value = os.getenv(name)
309*cc02d7e2SAndroid Build Coastguard Worker    if value is None:
310*cc02d7e2SAndroid Build Coastguard Worker        value = os.getenv(name.lower())
311*cc02d7e2SAndroid Build Coastguard Worker    return value
312*cc02d7e2SAndroid Build Coastguard Worker
313*cc02d7e2SAndroid Build Coastguard Worker
314*cc02d7e2SAndroid Build Coastguard Workerdef _get_argument_last_occurrence_or_none(protoc_arg_dict, name):
315*cc02d7e2SAndroid Build Coastguard Worker    # If argument was passed multiple times, take the last occurrence.
316*cc02d7e2SAndroid Build Coastguard Worker    # If the value does not exist then return None
317*cc02d7e2SAndroid Build Coastguard Worker    values = protoc_arg_dict.get(name)
318*cc02d7e2SAndroid Build Coastguard Worker    if values is not None:
319*cc02d7e2SAndroid Build Coastguard Worker        return values[-1]
320*cc02d7e2SAndroid Build Coastguard Worker    return None
321*cc02d7e2SAndroid Build Coastguard Worker
322*cc02d7e2SAndroid Build Coastguard Worker
323*cc02d7e2SAndroid Build Coastguard Workerdef main():
324*cc02d7e2SAndroid Build Coastguard Worker    # Check environment variables for the additional arguments used in the tests.
325*cc02d7e2SAndroid Build Coastguard Worker
326*cc02d7e2SAndroid Build Coastguard Worker    projectdir = _getenv("FAKEPROTOC_PROJECTDIR")
327*cc02d7e2SAndroid Build Coastguard Worker    if not projectdir:
328*cc02d7e2SAndroid Build Coastguard Worker        print("FAKEPROTOC_PROJECTDIR not set")
329*cc02d7e2SAndroid Build Coastguard Worker        sys.exit(1)
330*cc02d7e2SAndroid Build Coastguard Worker    projectdir = os.path.abspath(projectdir)
331*cc02d7e2SAndroid Build Coastguard Worker
332*cc02d7e2SAndroid Build Coastguard Worker    # Output directory for generated files and output file
333*cc02d7e2SAndroid Build Coastguard Worker    protoc_outdir = _getenv("FAKEPROTOC_OUTDIR")
334*cc02d7e2SAndroid Build Coastguard Worker    if not protoc_outdir:
335*cc02d7e2SAndroid Build Coastguard Worker        print("FAKEPROTOC_OUTDIR not set")
336*cc02d7e2SAndroid Build Coastguard Worker        sys.exit(1)
337*cc02d7e2SAndroid Build Coastguard Worker    protoc_outdir = os.path.abspath(protoc_outdir)
338*cc02d7e2SAndroid Build Coastguard Worker
339*cc02d7e2SAndroid Build Coastguard Worker    # Get list of expected generated files from env variable
340*cc02d7e2SAndroid Build Coastguard Worker    generate_expected = _getenv("FAKEPROTOC_GENERATE_EXPECTED")
341*cc02d7e2SAndroid Build Coastguard Worker    if not generate_expected:
342*cc02d7e2SAndroid Build Coastguard Worker        print("FAKEPROTOC_GENERATE_EXPECTED not set")
343*cc02d7e2SAndroid Build Coastguard Worker        sys.exit(1)
344*cc02d7e2SAndroid Build Coastguard Worker
345*cc02d7e2SAndroid Build Coastguard Worker    # Prepare the debug log
346*cc02d7e2SAndroid Build Coastguard Worker    log_dir = os.path.join(protoc_outdir, "log")
347*cc02d7e2SAndroid Build Coastguard Worker    if not os.path.isdir(log_dir):
348*cc02d7e2SAndroid Build Coastguard Worker        os.makedirs(log_dir)
349*cc02d7e2SAndroid Build Coastguard Worker    _open_debug_log("%s/fakeprotoc_log.txt" % log_dir)
350*cc02d7e2SAndroid Build Coastguard Worker
351*cc02d7e2SAndroid Build Coastguard Worker    _write_debug(
352*cc02d7e2SAndroid Build Coastguard Worker        (
353*cc02d7e2SAndroid Build Coastguard Worker            "##### fakeprotoc called at %s\n"
354*cc02d7e2SAndroid Build Coastguard Worker            + "FAKEPROTOC_PROJECTDIR = %s\n"
355*cc02d7e2SAndroid Build Coastguard Worker            + "FAKEPROTOC_GENERATE_EXPECTED = %s\n"
356*cc02d7e2SAndroid Build Coastguard Worker        )
357*cc02d7e2SAndroid Build Coastguard Worker        % (datetime.datetime.now(), projectdir, generate_expected)
358*cc02d7e2SAndroid Build Coastguard Worker    )
359*cc02d7e2SAndroid Build Coastguard Worker
360*cc02d7e2SAndroid Build Coastguard Worker    proto_to_generated = _parse_generate_expected(generate_expected)
361*cc02d7e2SAndroid Build Coastguard Worker    protoc_args = _read_protoc_arguments()
362*cc02d7e2SAndroid Build Coastguard Worker    protoc_arg_dict = _parse_protoc_arguments(protoc_args, projectdir)
363*cc02d7e2SAndroid Build Coastguard Worker
364*cc02d7e2SAndroid Build Coastguard Worker    # If argument was passed multiple times, take the last occurrence of it.
365*cc02d7e2SAndroid Build Coastguard Worker    # TODO(jtattermusch): handle multiple occurrences of the same argument
366*cc02d7e2SAndroid Build Coastguard Worker    dependencyfile = _get_argument_last_occurrence_or_none(
367*cc02d7e2SAndroid Build Coastguard Worker        protoc_arg_dict, "--dependency_out"
368*cc02d7e2SAndroid Build Coastguard Worker    )
369*cc02d7e2SAndroid Build Coastguard Worker    grpcout = _get_argument_last_occurrence_or_none(
370*cc02d7e2SAndroid Build Coastguard Worker        protoc_arg_dict, "--grpc_out"
371*cc02d7e2SAndroid Build Coastguard Worker    )
372*cc02d7e2SAndroid Build Coastguard Worker    csharpout = _get_argument_last_occurrence_or_none(
373*cc02d7e2SAndroid Build Coastguard Worker        protoc_arg_dict, "--csharp_out"
374*cc02d7e2SAndroid Build Coastguard Worker    )
375*cc02d7e2SAndroid Build Coastguard Worker
376*cc02d7e2SAndroid Build Coastguard Worker    # --grpc_out might not be set in which case use --csharp_out
377*cc02d7e2SAndroid Build Coastguard Worker    if grpcout is None:
378*cc02d7e2SAndroid Build Coastguard Worker        grpcout = csharpout
379*cc02d7e2SAndroid Build Coastguard Worker
380*cc02d7e2SAndroid Build Coastguard Worker    if len(protoc_arg_dict.get("protofile")) != 1:
381*cc02d7e2SAndroid Build Coastguard Worker        # regular protoc can process multiple .proto files passed at once, but we know
382*cc02d7e2SAndroid Build Coastguard Worker        # the Grpc.Tools msbuild integration only ever passes one .proto file per invocation.
383*cc02d7e2SAndroid Build Coastguard Worker        print(
384*cc02d7e2SAndroid Build Coastguard Worker            "Expecting to get exactly one .proto file argument per fakeprotoc"
385*cc02d7e2SAndroid Build Coastguard Worker            " invocation."
386*cc02d7e2SAndroid Build Coastguard Worker        )
387*cc02d7e2SAndroid Build Coastguard Worker        sys.exit(1)
388*cc02d7e2SAndroid Build Coastguard Worker    protofile = protoc_arg_dict.get("protofile")[0]
389*cc02d7e2SAndroid Build Coastguard Worker
390*cc02d7e2SAndroid Build Coastguard Worker    cs_files_to_generate = _get_cs_files_to_generate(
391*cc02d7e2SAndroid Build Coastguard Worker        protofile=protofile, proto_to_generated=proto_to_generated
392*cc02d7e2SAndroid Build Coastguard Worker    )
393*cc02d7e2SAndroid Build Coastguard Worker
394*cc02d7e2SAndroid Build Coastguard Worker    _create_dependency_file(
395*cc02d7e2SAndroid Build Coastguard Worker        protofile=protofile,
396*cc02d7e2SAndroid Build Coastguard Worker        cs_files_to_generate=cs_files_to_generate,
397*cc02d7e2SAndroid Build Coastguard Worker        dependencyfile=dependencyfile,
398*cc02d7e2SAndroid Build Coastguard Worker        grpc_out_dir=grpcout,
399*cc02d7e2SAndroid Build Coastguard Worker        csharp_out_dir=csharpout,
400*cc02d7e2SAndroid Build Coastguard Worker    )
401*cc02d7e2SAndroid Build Coastguard Worker
402*cc02d7e2SAndroid Build Coastguard Worker    _generate_cs_files(
403*cc02d7e2SAndroid Build Coastguard Worker        protofile=protofile,
404*cc02d7e2SAndroid Build Coastguard Worker        cs_files_to_generate=cs_files_to_generate,
405*cc02d7e2SAndroid Build Coastguard Worker        grpc_out_dir=grpcout,
406*cc02d7e2SAndroid Build Coastguard Worker        csharp_out_dir=csharpout,
407*cc02d7e2SAndroid Build Coastguard Worker        projectdir=projectdir,
408*cc02d7e2SAndroid Build Coastguard Worker    )
409*cc02d7e2SAndroid Build Coastguard Worker
410*cc02d7e2SAndroid Build Coastguard Worker    _write_or_update_results_json(
411*cc02d7e2SAndroid Build Coastguard Worker        log_dir=log_dir, protofile=protofile, protoc_arg_dict=protoc_arg_dict
412*cc02d7e2SAndroid Build Coastguard Worker    )
413*cc02d7e2SAndroid Build Coastguard Worker
414*cc02d7e2SAndroid Build Coastguard Worker    _close_debug_log()
415*cc02d7e2SAndroid Build Coastguard Worker
416*cc02d7e2SAndroid Build Coastguard Worker
417*cc02d7e2SAndroid Build Coastguard Workerif __name__ == "__main__":
418*cc02d7e2SAndroid Build Coastguard Worker    main()
419