jsii-pacmak
Version:
A code generation framework for jsii backend languages
215 lines • 8.71 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.Golang = void 0;
const codemaker_1 = require("codemaker");
const fs = require("fs-extra");
const path = require("path");
const logging = require("../logging");
const target_1 = require("../target");
const util_1 = require("../util");
const documentation_1 = require("./go/documentation");
const package_1 = require("./go/package");
const runtime_1 = require("./go/runtime");
const util_2 = require("./go/util");
class Golang extends target_1.Target {
constructor(options) {
super(options);
this.goGenerator = new GoGenerator(options);
}
get generator() {
return this.goGenerator;
}
/**
* Generates a publishable artifact in `outDir`.
*
* @param sourceDir the directory where the generated source is located.
* @param outDir the directory where the publishable artifact should be placed.
*/
async build(sourceDir, outDir) {
// copy generated sources to the output directory
await this.copyFiles(sourceDir, outDir);
const pkgDir = path.join(outDir, this.goGenerator.rootPackage.packageName);
// write `local.go.mod` with "replace" directives for local modules
const localGoMod = await this.writeLocalGoMod(pkgDir);
try {
// run `go build` with local.go.mod, go 1.16+ requires that we download
// modules explicit so go.sum is updated. We'd want to use
// `go mod download`, but it does not add missing entries in the `go.sum`
// file while `go mod tidy` does.
await go('mod', ['tidy', '-modfile', localGoMod.path], {
cwd: pkgDir,
});
}
catch (e) {
logging.info(`[${pkgDir}] Content of ${localGoMod.path} file:\n${localGoMod.content}`);
return Promise.reject(e);
}
if (process.env.JSII_BUILD_GO) {
// This step is taken to ensure that the generated code is compilable
await go('build', ['-modfile', localGoMod.path, './...'], {
cwd: pkgDir,
});
}
// delete local.go.mod and local.go.sum from the output directory so it doesn't get published
const localGoSum = `${path.basename(localGoMod.path, '.mod')}.sum`;
await fs.remove(path.join(pkgDir, localGoMod.path));
return fs.remove(path.join(pkgDir, localGoSum));
}
/**
* Creates a copy of the `go.mod` file called `local.go.mod` with added
* `replace` directives for local mono-repo dependencies. This is required in
* order to run `go fmt` and `go build`.
*
* @param pkgDir The directory which contains the generated go code
*/
async writeLocalGoMod(pkgDir) {
const replace = {};
// find local deps by check if `<jsii.outdir>/go` exists for dependencies
// and also consider `outDir` in case pacmak is executed using `--outdir
// --recurse` (in which case all go code will be generated there).
const dirs = [
path.dirname(pkgDir),
...(await (0, target_1.findLocalBuildDirs)(this.packageDir, 'go')),
];
// try to resolve @jsii/go-runtime (only exists as a devDependency)
const localModules = tryFindLocalRuntime();
if (localModules != null) {
for (const [name, localPath] of Object.entries(localModules)) {
replace[name] = localPath;
}
}
// iterate (recursively) on all package dependencies and check if we have a
// local build directory for this module. if
// we do, add a "replace" directive to point to it instead of download from
// the network.
const visit = async (pkg) => {
for (const dep of pkg.packageDependencies) {
for (const baseDir of dirs) {
// eslint-disable-next-line no-await-in-loop
const moduleDir = await tryFindLocalModule(baseDir, dep);
if (moduleDir) {
replace[dep.goModuleName] = moduleDir;
// we found a replacement for this dep, we can stop searching
break;
}
}
// recurse to transitive deps ("replace" is only considered at the top level go.mod)
// eslint-disable-next-line no-await-in-loop
await visit(dep);
}
};
await visit(this.goGenerator.rootPackage);
// write `local.go.mod`
// read existing content
const goMod = path.join(pkgDir, package_1.GOMOD_FILENAME);
const lines = [await fs.readFile(goMod, 'utf-8'), '', '// Local packages:'];
for (const [from, to] of Object.entries(replace)) {
logging.info(`[${pkgDir}] Local replace: ${from} => ${to}`);
lines.push(`replace ${from} => ${to}`);
}
const localGoMod = `local.${package_1.GOMOD_FILENAME}`;
const content = lines.join('\n');
await fs.writeFile(path.join(pkgDir, localGoMod), content, {
encoding: 'utf-8',
});
return { path: localGoMod, content };
}
}
exports.Golang = Golang;
class GoGenerator {
constructor(options) {
this.code = new codemaker_1.CodeMaker({
indentCharacter: '\t',
indentationLevel: 1,
});
this.rosetta = options.rosetta;
this.documenter = new documentation_1.Documentation(this.code, this.rosetta);
this.runtimeTypeChecking = options.runtimeTypeChecking;
}
async load(_, assembly) {
this.assembly = assembly;
return Promise.resolve();
}
async upToDate(_outDir) {
return Promise.resolve(false);
}
generate() {
this.rootPackage = new package_1.RootPackage(this.assembly);
return this.rootPackage.emit({
code: this.code,
documenter: this.documenter,
runtimeTypeChecking: this.runtimeTypeChecking,
});
}
async save(outDir, tarball, { license, notice }) {
const output = path.join(outDir, this.rootPackage.packageName);
await this.code.save(output);
await fs.copyFile(tarball, path.join(output, runtime_1.JSII_INIT_PACKAGE, (0, util_2.tarballName)(this.assembly)));
if (license) {
await fs.writeFile(path.join(output, 'LICENSE'), license, {
encoding: 'utf8',
});
}
if (notice) {
await fs.writeFile(path.join(output, 'NOTICE'), notice, {
encoding: 'utf8',
});
}
}
}
/**
* Checks if `buildDir` includes a local go build version (with "replace"
* directives).
* @param baseDir the `dist/go` directory
* @returns `undefined` if not or the module directory otherwise.
*/
async function tryFindLocalModule(baseDir, pkg) {
const gomodPath = path.join(baseDir, pkg.packageName, package_1.GOMOD_FILENAME);
if (!(await fs.pathExists(gomodPath))) {
return undefined;
}
// read `go.mod` and check that it is for the correct module
const gomod = (await fs.readFile(gomodPath, 'utf-8')).split('\n');
const isExpectedModule = gomod.find((line) => line.trim() === `module ${pkg.goModuleName}`);
if (!isExpectedModule) {
return undefined;
}
return path.resolve(path.dirname(gomodPath));
}
/**
* Check if we are running from inside the jsii repository, and then we want to
* use the local runtime instead of download from a released version.
*
* This is a generator that procudes an entry for each local module that
* is identified under the local module path exposed by `@jsii/go-runtime` .
*/
function tryFindLocalRuntime() {
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports, import/no-extraneous-dependencies
const localRuntime = require('@jsii/go-runtime');
logging.debug(`Using @jsii/go-runtime from ${localRuntime.runtimePath}`);
return localRuntime.runtimeModules;
}
catch {
return undefined;
}
}
/**
* Executes a go CLI command.
*
*
* @param command The `go` command to execute (e.g. `build`)
* @param args Additional args
* @param options Options
*/
async function go(command, args, options) {
const { cwd } = options;
return (0, util_1.shell)('go', [command, ...args], {
cwd,
env: {
// disable the use of sumdb to reduce eventual consistency issues when new modules are published
GOSUMDB: 'off',
},
});
}
//# sourceMappingURL=go.js.map
;