xref: /aosp_15_r20/external/pigweed/pw_protobuf_compiler/ts/build.ts (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1// Copyright 2022 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14
15import { exec, ExecException } from 'child_process';
16import fs from 'fs';
17import path from 'path';
18import generateTemplate from './codegen/template_replacement';
19const googProtobufPath = require.resolve('google-protobuf');
20const googProtobufModule = fs.readFileSync(googProtobufPath, 'utf-8');
21
22const run = function (
23  executable: string,
24  args: string[],
25  cwd: string = process.cwd(),
26) {
27  return new Promise<void>((resolve) => {
28    exec(
29      `${executable} ${args.join(' ')}`,
30      { cwd },
31      (error: ExecException | null, stdout: string | Buffer) => {
32        if (error) {
33          throw error;
34        }
35
36        console.log(stdout);
37        resolve();
38      },
39    );
40  });
41};
42
43function getRealPathOfSymlink(path: string) {
44  const stats = fs.statSync(path);
45  if (stats.isSymbolicLink()) {
46    return fs.realpathSync(path);
47  } else {
48    return path;
49  }
50}
51
52const protoc = async function (
53  protos: string[],
54  outDir: string,
55  cwd: string = process.cwd(),
56) {
57  const PROTOC_GEN_TS_PATH = getRealPathOfSymlink(
58    path.resolve(
59      path.dirname(require.resolve('ts-protoc-gen/generate.js')),
60      'bin',
61      'protoc-gen-ts',
62    ),
63  );
64
65  const protocBinary = require.resolve('@protobuf-ts/protoc/protoc.js');
66
67  await run(
68    protocBinary,
69    [
70      `--plugin="protoc-gen-ts=${PROTOC_GEN_TS_PATH}"`,
71      `--descriptor_set_out=${path.join(outDir, 'descriptor.bin')}`,
72      `--js_out=import_style=commonjs,binary:${outDir}`,
73      `--ts_out=${outDir}`,
74      `--proto_path=${cwd}`,
75      ...protos,
76    ],
77    cwd,
78  );
79
80  // ES6 workaround: Replace google-protobuf imports with entire library.
81  protos.forEach((protoPath) => {
82    const outPath = path.join(outDir, protoPath.replace('.proto', '_pb.js'));
83
84    if (fs.existsSync(outPath)) {
85      let data = fs.readFileSync(outPath, 'utf8');
86      data = data.replace(
87        "var jspb = require('google-protobuf');",
88        googProtobufModule,
89      );
90      data = data.replace('var goog = jspb;', '');
91      fs.writeFileSync(outPath, data);
92    }
93  });
94};
95
96const makeProtoCollection = function (
97  descriptorBinPath: string,
98  protoPath: string,
99  outputCollectionName: string,
100) {
101  generateTemplate(`${protoPath}/${outputCollectionName}`, descriptorBinPath);
102};
103
104export function buildProtos(
105  protos: string[],
106  outDir: string,
107  outputCollectionName = 'collection.js',
108  cwd: string = process.cwd(),
109) {
110  protoc(protos, outDir, cwd).then(() => {
111    makeProtoCollection(
112      path.join(outDir, 'descriptor.bin'),
113      outDir,
114      outputCollectionName,
115    );
116  });
117}
118