@platforma-sdk/tengo-builder 1.14.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -0
- package/bin/run.js +7 -0
- package/dist/commands/build.d.ts +13 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/check.d.ts +11 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/dump/all.d.ts +7 -0
- package/dist/commands/dump/all.d.ts.map +1 -0
- package/dist/commands/dump/libs.d.ts +10 -0
- package/dist/commands/dump/libs.d.ts.map +1 -0
- package/dist/commands/dump/software.d.ts +7 -0
- package/dist/commands/dump/software.d.ts.map +1 -0
- package/dist/commands/dump/templates.d.ts +7 -0
- package/dist/commands/dump/templates.d.ts.map +1 -0
- package/dist/commands/dump/tests.d.ts +7 -0
- package/dist/commands/dump/tests.d.ts.map +1 -0
- package/dist/commands/index.d.ts +9 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/test.d.ts +11 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/compiler/artifactset.d.ts +22 -0
- package/dist/compiler/artifactset.d.ts.map +1 -0
- package/dist/compiler/compiler.d.ts +34 -0
- package/dist/compiler/compiler.d.ts.map +1 -0
- package/dist/compiler/main.d.ts +17 -0
- package/dist/compiler/main.d.ts.map +1 -0
- package/dist/compiler/package.d.ts +37 -0
- package/dist/compiler/package.d.ts.map +1 -0
- package/dist/compiler/source.d.ts +27 -0
- package/dist/compiler/source.d.ts.map +1 -0
- package/dist/compiler/template.d.ts +49 -0
- package/dist/compiler/template.d.ts.map +1 -0
- package/dist/compiler/test.artifacts.d.ts +32 -0
- package/dist/compiler/test.artifacts.d.ts.map +1 -0
- package/dist/compiler/util.d.ts +5 -0
- package/dist/compiler/util.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +851 -0
- package/dist/index.mjs.map +1 -0
- package/dist/shared/basecmd.d.ts +9 -0
- package/dist/shared/basecmd.d.ts.map +1 -0
- package/dist/shared/dump.d.ts +7 -0
- package/dist/shared/dump.d.ts.map +1 -0
- package/dist/shared/proc.d.ts +5 -0
- package/dist/shared/proc.d.ts.map +1 -0
- package/package.json +44 -0
- package/src/commands/build.ts +175 -0
- package/src/commands/check.ts +45 -0
- package/src/commands/dump/all.ts +17 -0
- package/src/commands/dump/libs.ts +24 -0
- package/src/commands/dump/software.ts +17 -0
- package/src/commands/dump/templates.ts +18 -0
- package/src/commands/dump/tests.ts +17 -0
- package/src/commands/index.ts +10 -0
- package/src/commands/test.ts +41 -0
- package/src/compiler/artifactset.ts +76 -0
- package/src/compiler/compiler.test.ts +48 -0
- package/src/compiler/compiler.ts +300 -0
- package/src/compiler/main.ts +363 -0
- package/src/compiler/package.ts +96 -0
- package/src/compiler/source.test.ts +28 -0
- package/src/compiler/source.ts +319 -0
- package/src/compiler/template.test.ts +54 -0
- package/src/compiler/template.ts +90 -0
- package/src/compiler/test.artifacts.ts +195 -0
- package/src/compiler/util.ts +38 -0
- package/src/index.ts +1 -0
- package/src/shared/basecmd.ts +28 -0
- package/src/shared/dump.ts +164 -0
- package/src/shared/proc.ts +29 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { createLogger } from '../compiler/main';
|
|
3
|
+
import { dumpAll } from '../shared/dump';
|
|
4
|
+
import { GlobalFlags } from '../shared/basecmd';
|
|
5
|
+
import { spawnEmbed, waitFor } from '../shared/proc';
|
|
6
|
+
import { TengoTesterBinaryPath } from '@milaboratories/tengo-tester';
|
|
7
|
+
|
|
8
|
+
export default class Test extends Command {
|
|
9
|
+
static override description = 'run tengo unit tests (.test.tengo)';
|
|
10
|
+
|
|
11
|
+
static strict = false;
|
|
12
|
+
|
|
13
|
+
static override flags = { ...GlobalFlags };
|
|
14
|
+
|
|
15
|
+
static override examples = ['<%= config.bin %> <%= command.id %>'];
|
|
16
|
+
|
|
17
|
+
public async run(): Promise<void> {
|
|
18
|
+
const { flags } = await this.parse(Test);
|
|
19
|
+
const logger = createLogger(flags['log-level']);
|
|
20
|
+
|
|
21
|
+
const testerArgs: string[] = this.argv.length == 0 ? ['./src'] : this.argv;
|
|
22
|
+
|
|
23
|
+
// prettier-ignore
|
|
24
|
+
const tester = spawnEmbed(
|
|
25
|
+
TengoTesterBinaryPath,
|
|
26
|
+
'run', '--log-level', flags['log-level'],
|
|
27
|
+
'--artifacts', '-',
|
|
28
|
+
...testerArgs,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
dumpAll(logger, tester.stdin);
|
|
33
|
+
} catch (err: unknown) {
|
|
34
|
+
logger.error(err);
|
|
35
|
+
} finally {
|
|
36
|
+
tester.stdin.end();
|
|
37
|
+
const code = await waitFor(tester);
|
|
38
|
+
process.exit(code);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { CompileMode, TypedArtifactName, artifactKey } from './package';
|
|
2
|
+
import { assertNever } from './util';
|
|
3
|
+
|
|
4
|
+
export class ArtifactMap<T> {
|
|
5
|
+
private readonly map = new Map<string, T>();
|
|
6
|
+
|
|
7
|
+
constructor(private readonly nameExtractor: (obj: T) => TypedArtifactName) {
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
add(obj: T, replace: boolean = true): T | undefined {
|
|
11
|
+
const key = artifactKey(this.nameExtractor(obj));
|
|
12
|
+
const ret = this.map.get(key);
|
|
13
|
+
if (ret && !replace)
|
|
14
|
+
return ret;
|
|
15
|
+
this.map.set(key, obj);
|
|
16
|
+
return ret;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get(name: TypedArtifactName): T | undefined {
|
|
20
|
+
return this.map.get(artifactKey(name));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get array(): T[] {
|
|
24
|
+
const ret: T[] = [];
|
|
25
|
+
this.map.forEach(obj => ret.push(obj));
|
|
26
|
+
return ret;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
forEach(callback: (value: T, key: TypedArtifactName) => void) {
|
|
30
|
+
this.map.forEach(v => callback(v, this.nameExtractor(v)));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function createArtifactNameSet(): ArtifactMap<TypedArtifactName> {
|
|
35
|
+
return new ArtifactMap<TypedArtifactName>(obj => obj);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class ArtifactStore<T> {
|
|
39
|
+
private readonly dev: ArtifactMap<T>
|
|
40
|
+
private readonly dist: ArtifactMap<T>
|
|
41
|
+
|
|
42
|
+
constructor(private readonly nameExtractor: (obj: T) => TypedArtifactName) {
|
|
43
|
+
this.dev = new ArtifactMap<T>(nameExtractor)
|
|
44
|
+
this.dist = new ArtifactMap<T>(nameExtractor)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
add(mode: CompileMode, obj: T, replace: boolean = true): T | undefined {
|
|
48
|
+
switch (mode) {
|
|
49
|
+
case 'dist':
|
|
50
|
+
return this.dist.add(obj, replace)
|
|
51
|
+
|
|
52
|
+
default:
|
|
53
|
+
assertNever(mode)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
get(mode: CompileMode, name: TypedArtifactName): T | undefined {
|
|
58
|
+
switch (mode) {
|
|
59
|
+
case 'dist':
|
|
60
|
+
return this.dist.get(name);
|
|
61
|
+
|
|
62
|
+
default:
|
|
63
|
+
assertNever(mode)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
array(mode: CompileMode): T[] {
|
|
68
|
+
const ret: T[] = [];
|
|
69
|
+
this.forEach(mode, obj => ret.push(obj));
|
|
70
|
+
return ret;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
forEach(mode: CompileMode, callback: (value: T, key: TypedArtifactName) => void) {
|
|
74
|
+
this.dist.forEach( (obj, k) => callback(this.get(mode, k) ?? obj, k) )
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { TengoTemplateCompiler } from './compiler';
|
|
2
|
+
import { ArtifactSource, parseSource } from './source';
|
|
3
|
+
import {
|
|
4
|
+
TestArtifactSource, testLocalPackage,
|
|
5
|
+
testPackage1,
|
|
6
|
+
testPackage1Lib1Name,
|
|
7
|
+
testPackage1Lib1Src, testPackage1Lib2Name, testPackage1Lib2Src,
|
|
8
|
+
testPackage1Soft1Name,
|
|
9
|
+
testPackage1Soft1Src,
|
|
10
|
+
testPackage1Tpl3CompiledBase64, testPackage1Tpl3Name
|
|
11
|
+
} from './test.artifacts';
|
|
12
|
+
import { artifactNameToString } from './package';
|
|
13
|
+
import { Template } from './template';
|
|
14
|
+
|
|
15
|
+
function parseSrc(src: TestArtifactSource[]): ArtifactSource[] {
|
|
16
|
+
return src.map(tp => {
|
|
17
|
+
const aSrc = parseSource('dist', tp.src, tp.fullName, true);
|
|
18
|
+
return aSrc;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test('compile package 1', () => {
|
|
23
|
+
const compiler = new TengoTemplateCompiler('dist');
|
|
24
|
+
const compiled = compiler.compileAndAdd(parseSrc(testPackage1));
|
|
25
|
+
expect(compiled.templates[0].data.libs).toHaveProperty(artifactNameToString(testPackage1Lib1Name));
|
|
26
|
+
console.log(Buffer.from(compiled.templates[0].content).toString('base64'));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('compile main source set', () => {
|
|
30
|
+
const compiler = new TengoTemplateCompiler('dist');
|
|
31
|
+
|
|
32
|
+
// emulate adding compiled artifacts
|
|
33
|
+
compiler.addLib(parseSource('dist', testPackage1Lib1Src, testPackage1Lib1Name, true));
|
|
34
|
+
compiler.addLib(parseSource('dist', testPackage1Lib2Src, testPackage1Lib2Name, true));
|
|
35
|
+
compiler.addSoftware(parseSource('dist', testPackage1Soft1Src, testPackage1Soft1Name, true));
|
|
36
|
+
compiler.addTemplate(new Template('dist', testPackage1Tpl3Name, { content: Buffer.from(testPackage1Tpl3CompiledBase64, 'base64') }));
|
|
37
|
+
|
|
38
|
+
// all elements in the context must have all their dependencies met
|
|
39
|
+
compiler.checkLibs();
|
|
40
|
+
|
|
41
|
+
// main package compilation
|
|
42
|
+
const compiled = compiler.compileAndAdd(parseSrc(testLocalPackage));
|
|
43
|
+
const tpl1 = compiled.templates.find(t => t.fullName.id === 'local-template-1')!;
|
|
44
|
+
expect(tpl1).toBeDefined();
|
|
45
|
+
|
|
46
|
+
// checking that transient library dependency was resolved
|
|
47
|
+
expect(tpl1.data.templates).toHaveProperty('package1:template-3');
|
|
48
|
+
});
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { ArtifactSource } from './source';
|
|
2
|
+
import { Template, TemplateData } from './template';
|
|
3
|
+
import {
|
|
4
|
+
TypedArtifactName,
|
|
5
|
+
artifactKey,
|
|
6
|
+
fullNameToString,
|
|
7
|
+
typedArtifactNameToString,
|
|
8
|
+
artifactNameToString,
|
|
9
|
+
formatArtefactNameAndVersion, typedArtifactNamesEquals, FullArtifactName,
|
|
10
|
+
CompileMode
|
|
11
|
+
} from './package';
|
|
12
|
+
import { ArtifactStore } from './artifactset';
|
|
13
|
+
import { assertNever } from './util';
|
|
14
|
+
|
|
15
|
+
export interface TemplatesAndLibs {
|
|
16
|
+
templates: Template[],
|
|
17
|
+
libs: ArtifactSource[],
|
|
18
|
+
software: ArtifactSource[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class TengoTemplateCompiler {
|
|
22
|
+
constructor(
|
|
23
|
+
private readonly compileMode: CompileMode
|
|
24
|
+
) { }
|
|
25
|
+
|
|
26
|
+
private readonly libs = new ArtifactStore<ArtifactSource>(src => src.fullName);
|
|
27
|
+
private readonly software = new ArtifactStore<ArtifactSource>(src => src.fullName);
|
|
28
|
+
private readonly templates = new ArtifactStore<Template>(tpl => tpl.fullName);
|
|
29
|
+
|
|
30
|
+
private populateTemplateDataFromDependencies(fullName: FullArtifactName,
|
|
31
|
+
data: TemplateData,
|
|
32
|
+
deps: TypedArtifactName[],
|
|
33
|
+
trace: string[]) {
|
|
34
|
+
for (const dep of deps) {
|
|
35
|
+
switch (dep.type) {
|
|
36
|
+
case 'library':
|
|
37
|
+
const lib = this.getLibOrError(dep);
|
|
38
|
+
|
|
39
|
+
const recursionStart = trace.indexOf(artifactNameToString(dep))
|
|
40
|
+
if (recursionStart >= 0) {
|
|
41
|
+
let errorMessage = `library import recursion detected: ${trace.slice(recursionStart).join(" -> ")} -> ${artifactNameToString(dep)}`
|
|
42
|
+
throw new Error(errorMessage)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
data.libs[artifactNameToString(dep)] = {
|
|
46
|
+
...formatArtefactNameAndVersion(lib.fullName),
|
|
47
|
+
src: lib.src
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// populate with transient library dependencies
|
|
51
|
+
this.populateTemplateDataFromDependencies(fullName, data, lib.dependencies, [...trace, artifactNameToString(dep)]);
|
|
52
|
+
|
|
53
|
+
break;
|
|
54
|
+
case 'software':
|
|
55
|
+
const software = this.getSoftwareOrError(dep);
|
|
56
|
+
data.software[artifactNameToString(dep)] = {
|
|
57
|
+
...formatArtefactNameAndVersion(software.fullName),
|
|
58
|
+
src: software.src
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
break;
|
|
62
|
+
case 'template':
|
|
63
|
+
if (typedArtifactNamesEquals(fullName, dep))
|
|
64
|
+
// skipping self reference
|
|
65
|
+
continue;
|
|
66
|
+
|
|
67
|
+
const tpl = this.getTemplateOrError(dep);
|
|
68
|
+
data.templates[artifactNameToString(dep)] = tpl.data;
|
|
69
|
+
break;
|
|
70
|
+
case 'test':
|
|
71
|
+
throw new Error(
|
|
72
|
+
`dependencies tree error: tests should never be part of template: ${typedArtifactNameToString(dep)} is dependency of ${artifactNameToString(fullName)}`,
|
|
73
|
+
)
|
|
74
|
+
default:
|
|
75
|
+
assertNever(dep.type);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** This method assumes that all dependencies are already added to the compiler's context */
|
|
81
|
+
private compileAndAddTemplate(tplSrc: ArtifactSource): Template {
|
|
82
|
+
if (tplSrc.fullName.type !== 'template')
|
|
83
|
+
throw new Error('unexpected source type');
|
|
84
|
+
|
|
85
|
+
// creating template with unpopulated dependencies
|
|
86
|
+
const tplData: TemplateData = {
|
|
87
|
+
type: 'pl.tengo-template.v2',
|
|
88
|
+
...formatArtefactNameAndVersion(tplSrc.fullName),
|
|
89
|
+
templates: {},
|
|
90
|
+
libs: {},
|
|
91
|
+
software: {},
|
|
92
|
+
src: tplSrc.src
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// collecting dependencies in output format
|
|
96
|
+
this.populateTemplateDataFromDependencies(tplSrc.fullName, tplData, tplSrc.dependencies, []);
|
|
97
|
+
|
|
98
|
+
const tpl = new Template(tplSrc.compileMode, tplSrc.fullName, { data: tplData });
|
|
99
|
+
this.addTemplate(tpl);
|
|
100
|
+
return tpl;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
addLib(lib: ArtifactSource) {
|
|
104
|
+
const libFromMap = this.libs.add(lib.compileMode, lib, false)
|
|
105
|
+
if (libFromMap)
|
|
106
|
+
throw new Error(
|
|
107
|
+
`compiler already contain such library: adding = ${fullNameToString(lib.fullName)}, contains = ${fullNameToString(libFromMap.fullName)}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
allLibs(): ArtifactSource[] {
|
|
112
|
+
return this.libs.array(this.compileMode)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
getLib(name: TypedArtifactName): ArtifactSource | undefined {
|
|
116
|
+
if (name.type !== 'library')
|
|
117
|
+
throw new Error(`illegal artifact type: got ${name.type} instead of 'library`);
|
|
118
|
+
return this.libs.get(this.compileMode, name);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
getLibOrError(name: TypedArtifactName): ArtifactSource {
|
|
122
|
+
const lib = this.getLib(name);
|
|
123
|
+
if (!lib)
|
|
124
|
+
throw new Error(`library not found: ${artifactNameToString(name)}`);
|
|
125
|
+
return lib;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
addSoftware(software: ArtifactSource) {
|
|
129
|
+
const swFromMap = this.software.add(software.compileMode, software, false)
|
|
130
|
+
if (swFromMap)
|
|
131
|
+
throw new Error(
|
|
132
|
+
`compiler already contain info for software: adding = ${fullNameToString(software.fullName)}, contains = ${fullNameToString(swFromMap.fullName)}`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
allSoftware(): ArtifactSource[] {
|
|
137
|
+
return this.software.array(this.compileMode)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
getSoftware(name: TypedArtifactName): ArtifactSource | undefined {
|
|
141
|
+
if (name.type !== 'software')
|
|
142
|
+
throw new Error(`illegal artifact type: got ${name.type} instead of 'software`);
|
|
143
|
+
|
|
144
|
+
return this.software.get(this.compileMode, name);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
getSoftwareOrError(name: TypedArtifactName): ArtifactSource {
|
|
148
|
+
const software = this.getSoftware(name);
|
|
149
|
+
if (!software)
|
|
150
|
+
throw new Error(`software info not found: ${artifactNameToString(name)}`);
|
|
151
|
+
return software;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
addTemplate(tpl: Template) {
|
|
155
|
+
const tplFromMap = this.templates.add(tpl.compileMode, tpl, false);
|
|
156
|
+
if (tplFromMap)
|
|
157
|
+
throw new Error(
|
|
158
|
+
`compiler already contain such template: adding = ${fullNameToString(tpl.fullName)}, contains = ${fullNameToString(tplFromMap.fullName)}`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
allTemplates(): Template[] {
|
|
163
|
+
return this.templates.array(this.compileMode)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
getTemplate(name: TypedArtifactName): Template | undefined {
|
|
167
|
+
if (name.type !== 'template')
|
|
168
|
+
throw new Error(`illegal artifact type: got ${name.type} instead of 'template`);
|
|
169
|
+
return this.templates.get(this.compileMode, name);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
getTemplateOrError(name: TypedArtifactName): Template {
|
|
173
|
+
const tpl = this.getTemplate(name);
|
|
174
|
+
if (!tpl)
|
|
175
|
+
throw new Error(`template not found: ${artifactNameToString(name)}`);
|
|
176
|
+
return tpl;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
getArtefact(name: TypedArtifactName): ArtifactSource | Template | undefined {
|
|
180
|
+
switch (name.type) {
|
|
181
|
+
case 'template':
|
|
182
|
+
return this.getTemplate(name);
|
|
183
|
+
case 'library':
|
|
184
|
+
return this.getLib(name);
|
|
185
|
+
case 'software':
|
|
186
|
+
return this.getSoftware(name);
|
|
187
|
+
case 'test':
|
|
188
|
+
// Tests are ignored by the complier. They should never be compiled into templates or libs and
|
|
189
|
+
// should never be a dependency.
|
|
190
|
+
return undefined;
|
|
191
|
+
default:
|
|
192
|
+
assertNever(name.type);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
checkLibs() {
|
|
197
|
+
this.libs.forEach(this.compileMode, lib => {
|
|
198
|
+
for (const dep of lib.dependencies) {
|
|
199
|
+
if (dep.type === 'test')
|
|
200
|
+
throw new Error(`test should never be dependency of production code: ${typedArtifactNameToString(dep)} test is dependency of ${fullNameToString(lib.fullName)}`);
|
|
201
|
+
|
|
202
|
+
if (!this.getArtefact(dep))
|
|
203
|
+
throw new Error(`unresolved dependency ${typedArtifactNameToString(dep)} for ${fullNameToString(lib.fullName)}`);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
compileAndAdd(sources: ArtifactSource[]): TemplatesAndLibs {
|
|
209
|
+
const ret: TemplatesAndLibs = { templates: [], libs: [], software: [] };
|
|
210
|
+
let current: ArtifactSource[] = [];
|
|
211
|
+
|
|
212
|
+
for (const src of sources) {
|
|
213
|
+
if (src.fullName.type === 'library') {
|
|
214
|
+
// add libraries 'as-is' to be able to resolve them as dependencies
|
|
215
|
+
this.addLib(src);
|
|
216
|
+
ret.libs.push(src);
|
|
217
|
+
} else if (src.fullName.type === 'software') {
|
|
218
|
+
// add software 'as-is' to be able to resolve them as dependencies
|
|
219
|
+
this.addSoftware(src);
|
|
220
|
+
ret.software.push(src);
|
|
221
|
+
} else {
|
|
222
|
+
current.push(src)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
while (current.length > 0) {
|
|
227
|
+
const unprocessed: { src: ArtifactSource, err: Error }[] = [];
|
|
228
|
+
|
|
229
|
+
for (const src of current) {
|
|
230
|
+
//
|
|
231
|
+
// If one of the dependencies can't be resolved with current compiler context,
|
|
232
|
+
// we put aside the source until next iteration, in hope that the dependency
|
|
233
|
+
// will be satisfied then.
|
|
234
|
+
//
|
|
235
|
+
// This is equivalent to topological sorting of input sources.
|
|
236
|
+
//
|
|
237
|
+
const unsatisfied = src.dependencies.filter(dep =>
|
|
238
|
+
!this.getArtefact(dep) &&
|
|
239
|
+
// allow self reference for templates
|
|
240
|
+
!(src.fullName.type === 'template' && typedArtifactNamesEquals(src.fullName, dep))
|
|
241
|
+
)
|
|
242
|
+
if (unsatisfied.length > 0) {
|
|
243
|
+
let errorMessage = `Unsatisfied dependencies in ${fullNameToString(src.fullName)}:\n`
|
|
244
|
+
for (const dep of unsatisfied) {
|
|
245
|
+
errorMessage += ` - ${typedArtifactNameToString(dep)}\n`;
|
|
246
|
+
}
|
|
247
|
+
unprocessed.push({ src, err: Error(errorMessage) })
|
|
248
|
+
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// type specific processing
|
|
253
|
+
switch (src.fullName.type) {
|
|
254
|
+
case 'library':
|
|
255
|
+
// libraries are added as is
|
|
256
|
+
this.addLib(src);
|
|
257
|
+
ret.libs.push(src);
|
|
258
|
+
break;
|
|
259
|
+
case 'software':
|
|
260
|
+
// software dependencies are added as is
|
|
261
|
+
this.addSoftware(src);
|
|
262
|
+
ret.software.push(src);
|
|
263
|
+
break;
|
|
264
|
+
case 'template':
|
|
265
|
+
// templates are compiled and then added
|
|
266
|
+
try {
|
|
267
|
+
const tpl = this.compileAndAddTemplate(src);
|
|
268
|
+
ret.templates.push(tpl);
|
|
269
|
+
} catch (err: any) {
|
|
270
|
+
let errorMessage = `Unsatisfied dependencies in ${fullNameToString(src.fullName)}:\n`
|
|
271
|
+
errorMessage += ` - ${err.message}\n`;
|
|
272
|
+
|
|
273
|
+
unprocessed.push({ src, err: Error(errorMessage) }) // one or more dependencies are not resolvable yet
|
|
274
|
+
}
|
|
275
|
+
break;
|
|
276
|
+
case 'test':
|
|
277
|
+
// Ignore tests: they never should be part of compiled code or be a dependency.
|
|
278
|
+
break;
|
|
279
|
+
default:
|
|
280
|
+
assertNever(src.fullName.type);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// checking that we successfully added at least one source,
|
|
285
|
+
// if not all the source files in unprocessed array have unmet dependencies
|
|
286
|
+
if (current.length === unprocessed.length) {
|
|
287
|
+
let errorMessage = '';
|
|
288
|
+
|
|
289
|
+
for (const u of unprocessed) {
|
|
290
|
+
errorMessage += `\n${u.err.message}`
|
|
291
|
+
}
|
|
292
|
+
throw new Error(errorMessage);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
current = unprocessed.map(({ src: ArtifactSource }) => ArtifactSource);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return ret;
|
|
299
|
+
}
|
|
300
|
+
}
|