• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
52function getJSPluginPath() {
53  const EXT = process.platform === 'win32' ? '.exe' : '';
54  const pluginPath = getRealPathOfSymlink(
55    path.resolve(
56      path.dirname(require.resolve('protoc-gen-js')),
57      'bin',
58      'protoc-gen-js' + EXT,
59    ),
60  );
61  return pluginPath;
62}
63
64const protoc = async function (
65  protos: string[],
66  outDir: string,
67  cwd: string = process.cwd(),
68) {
69  const PROTOC_GEN_TS_PATH = getRealPathOfSymlink(
70    path.resolve(
71      path.dirname(require.resolve('ts-protoc-gen/generate.js')),
72      'bin',
73      'protoc-gen-ts',
74    ),
75  );
76  const PROTOC_GEN_JS_PATH = getJSPluginPath();
77
78  const protocBinary = require.resolve('@protobuf-ts/protoc/protoc.js');
79
80  await run(
81    protocBinary,
82    [
83      `--plugin="protoc-gen-ts=${PROTOC_GEN_TS_PATH}"`,
84      `--plugin="protoc-gen-js=${PROTOC_GEN_JS_PATH}"`,
85      `--descriptor_set_out=${path.join(outDir, 'descriptor.bin')}`,
86      `--js_out=import_style=commonjs,binary:${outDir}`,
87      `--ts_out=${outDir}`,
88      `--proto_path=${cwd}`,
89      ...protos,
90    ],
91    cwd,
92  );
93
94  // ES6 workaround: Replace google-protobuf imports with entire library.
95  protos.forEach((protoPath) => {
96    const outPath = path.join(outDir, protoPath.replace('.proto', '_pb.js'));
97
98    if (fs.existsSync(outPath)) {
99      let data = fs.readFileSync(outPath, 'utf8');
100      data = data.replace(
101        "var jspb = require('google-protobuf');",
102        googProtobufModule,
103      );
104      data = data.replace('var goog = jspb;', '');
105      fs.writeFileSync(outPath, data);
106    }
107  });
108};
109
110const makeProtoCollection = function (
111  descriptorBinPath: string,
112  protoPath: string,
113  outputCollectionName: string,
114) {
115  generateTemplate(`${protoPath}/${outputCollectionName}`, descriptorBinPath);
116};
117
118export function buildProtos(
119  protos: string[],
120  outDir: string,
121  outputCollectionName = 'collection.js',
122  cwd: string = process.cwd(),
123) {
124  protoc(protos, outDir, cwd).then(() => {
125    makeProtoCollection(
126      path.join(outDir, 'descriptor.bin'),
127      outDir,
128      outputCollectionName,
129    );
130  });
131}
132