Skip to content

Commit 1b35fab

Browse files
authored
Merge pull request #120 from IBM/change/iproj_usage
Support `actions.json` files as part of makefile generation
2 parents dcdfd62 + 263b879 commit 1b35fab

30 files changed

+429
-122
lines changed

cli/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/src/builders/actions/index.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { ReadFileSystem } from "../../readFileSystem";
2+
import { Targets } from "../../targets";
3+
import path from "path";
4+
5+
export interface Action {
6+
name: string,
7+
command: string,
8+
environment: "ile",
9+
extensions: string[],
10+
}
11+
12+
export class ProjectActions {
13+
private actions: { [relativePath: string]: Action[] } = {};
14+
15+
constructor(private readonly targets: Targets, private readonly readFileSystem: ReadFileSystem) {}
16+
17+
get getActionPaths() {
18+
return Object.keys(this.actions);
19+
}
20+
21+
public async loadAllActions() {
22+
const cwd = this.targets.getCwd();
23+
const files = await this.readFileSystem.getFiles(cwd, `**/actions.json`, {dot: true});
24+
25+
for (const file of files) {
26+
const relativePath = path.relative(cwd, file);
27+
const contents = await this.readFileSystem.readFile(file);
28+
try {
29+
const possibleActions = JSON.parse(contents) as Partial<Action[]>;
30+
31+
for (const possibleAction of possibleActions) {
32+
if (!possibleAction.name || !possibleAction.command || !possibleAction.environment || !possibleAction.extensions) {
33+
// TODO: Log a warning about missing required fields
34+
continue; // Skip if required fields are missing
35+
}
36+
37+
possibleAction.extensions = possibleAction.extensions.map(ext => ext.toLowerCase());
38+
}
39+
40+
this.actions[relativePath] = possibleActions as Action[];
41+
} catch (e) {
42+
console.log(`Error parsing actions.json at ${relativePath}:`, e);
43+
}
44+
}
45+
46+
const vscodePath = path.join(`.vscode`, `actions.json`);
47+
if (this.actions[vscodePath]) {
48+
// If there is a .vscode/actions.json, it is the project actions
49+
this.actions[`actions.json`] = this.actions[vscodePath];
50+
delete this.actions[vscodePath];
51+
}
52+
}
53+
54+
getActionForPath(relativeSourcePath: string): Action|undefined {
55+
let allPossibleActions: Action[] = [];
56+
let parent: string = relativeSourcePath;
57+
58+
while (true) {
59+
parent = path.dirname(parent);
60+
61+
const actionFile = path.join(parent, `actions.json`);
62+
63+
if (this.actions[actionFile]) {
64+
allPossibleActions.push(...this.actions[actionFile]);
65+
}
66+
67+
if (parent === `.`) {
68+
// Reached the root directory, stop searching
69+
break;
70+
}
71+
}
72+
73+
let extension = path.extname(relativeSourcePath).toLowerCase();
74+
75+
if (extension.startsWith(`.`)) {
76+
extension = extension.slice(1);
77+
}
78+
79+
allPossibleActions = allPossibleActions.filter((action) => {
80+
return action.extensions.includes(extension);
81+
});
82+
83+
return allPossibleActions[0];
84+
}
85+
}

cli/src/builders/environment.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { str } from "crc-32/crc32c";
22
import { ObjectType } from "../targets";
3+
import path from "path";
34

45
// Always try and store parmId as lowercase
56
export type CommandParameters = { [parmId: string]: string };
@@ -43,6 +44,22 @@ export function extCanBeProgram(ext: string): boolean {
4344
return ([`MODULE`, `PGM`].includes(getObjectType(ext)));
4445
}
4546

47+
export function getTrueBasename(name: string) {
48+
// Logic to handle second extension, caused by bob.
49+
const sourceObjectTypes = [`.PGM`, `.SRVPGM`, `.TEST`];
50+
const secondName = path.parse(name);
51+
if (secondName.ext && sourceObjectTypes.includes(secondName.ext.toUpperCase())) {
52+
name = secondName.name;
53+
}
54+
55+
// Remove bob text convention
56+
if (name.includes(`-`)) {
57+
name = name.substring(0, name.indexOf(`-`));
58+
}
59+
60+
return name;
61+
}
62+
4663
export function getObjectType(ext: string): ObjectType {
4764
switch (ext.toLowerCase()) {
4865
case `dspf`:

cli/src/builders/iProject.ts

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { fromCl } from "../utils";
2-
import { CompileData, CommandParameters, CompileAttribute, getDefaultCompiles, Action, getObjectType } from "./environment";
1+
import { CommandParameters, CompileAttribute, getDefaultCompiles } from "./environment";
32

43
export class iProject {
54
includePaths?: string[] = [];
@@ -33,18 +32,4 @@ export class iProject {
3332
}
3433
}
3534
}
36-
37-
applyAction(newAction: Action) {
38-
if (newAction.environment && newAction.environment === `ile` && newAction.extensions && newAction.extensions.length > 0) {
39-
if (!newAction.extensions.includes(`GLOBAL`)) {
40-
const firstExt = newAction.extensions[0].toLowerCase();
41-
const becomesObject = getObjectType(firstExt);
42-
const commandData = fromCl(newAction.command);
43-
this.compiles[firstExt] = {
44-
becomes: becomesObject,
45-
...commandData
46-
};
47-
}
48-
}
49-
}
5035
}

cli/src/builders/make/customRules.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as path from "path";
66
import { MakeProject } from ".";
77

88
/**
9-
* Scan for all rules.mk files and read attributes and custom
9+
* Scan for all ibmi-bob rules.mk files and read attributes and custom
1010
* dependencies into the targets.
1111
*/
1212
export function readAllRules(targets: Targets, project: MakeProject) {

cli/src/builders/make/index.ts

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,37 @@
11
import { existsSync, readFileSync } from 'fs';
22
import path from 'path';
33
import { ILEObject, ILEObjectTarget, ImpactedObject, ObjectType, Targets } from '../../targets';
4-
import { asPosix, getFiles, toCl } from '../../utils';
4+
import { asPosix, fromCl, getFiles, toCl } from '../../utils';
55
import { warningOut } from '../../cli';
66
import { name } from '../../../webpack.config';
77
import { FolderOptions, getFolderOptions } from './folderSettings';
88
import { readAllRules } from './customRules';
9-
import { CompileData, CommandParameters } from '../environment';
9+
import { CompileData, CommandParameters, getTrueBasename } from '../environment';
1010
import { iProject } from '../iProject';
11+
import { ReadFileSystem } from '../../readFileSystem';
12+
import { ProjectActions } from '../actions';
1113

1214
export class MakeProject {
1315
private noChildren: boolean = false;
1416
private settings: iProject = new iProject();
17+
private projectActions: ProjectActions;
18+
1519
private folderSettings: {[folder: string]: FolderOptions} = {};
1620

17-
constructor(private cwd: string, private targets: Targets) {
18-
this.setupSettings();
21+
constructor(private cwd: string, private targets: Targets, private rfs: ReadFileSystem) {
22+
this.projectActions = new ProjectActions(this.targets, this.rfs);
1923
}
2024

2125
public setNoChildrenInBuild(noChildren: boolean) {
2226
this.noChildren = noChildren;
2327
}
2428

25-
private setupSettings() {
29+
async setupSettings() {
30+
await this.projectActions.loadAllActions();
31+
2632
// First, let's setup the project settings
2733
try {
28-
const content = readFileSync(path.join(this.cwd, `iproj.json`), { encoding: `utf-8` });
34+
const content = await this.rfs.readFile(path.join(this.cwd, `iproj.json`));
2935
const asJson: iProject = JSON.parse(content);
3036

3137
this.settings.applySettings(asJson);
@@ -168,7 +174,7 @@ export class MakeProject {
168174
let lines = [];
169175

170176
for (const entry of Object.entries(this.settings.compiles)) {
171-
const [type, data] = entry;
177+
let [type, data] = entry;
172178

173179
// commandSource means 'is this object built from CL commands in a file'
174180
if (data.commandSource) {
@@ -213,7 +219,22 @@ export class MakeProject {
213219
// This is used when your object really has source
214220

215221
const possibleTarget: ILEObjectTarget = this.targets.getTarget(ileObject) || (ileObject as ILEObjectTarget);
216-
const customAttributes = this.getObjectAttributes(data, possibleTarget);
222+
let customAttributes = this.getObjectAttributes(data, possibleTarget);
223+
224+
if (ileObject.relativePath) {
225+
const possibleAction = this.projectActions.getActionForPath(ileObject.relativePath);
226+
if (possibleAction) {
227+
const clData = fromCl(possibleAction.command);
228+
// If there is an action for this object, we want to apply the action's parameters
229+
// to the custom attributes.
230+
231+
data = {
232+
...data,
233+
command: clData.command,
234+
parameters: clData.parameters
235+
}
236+
}
237+
}
217238

218239
lines.push(...MakeProject.generateSpecificTarget(data, possibleTarget, customAttributes));
219240
}
@@ -250,12 +271,30 @@ export class MakeProject {
250271
const parentName = ileObject.relativePath ? path.dirname(ileObject.relativePath) : undefined;
251272
const qsysTempName: string | undefined = (parentName && parentName.length > 10 ? parentName.substring(0, 10) : parentName);
252273

274+
const simpleReplace = (str: string, search: string, replace: string) => {
275+
return str.replace(new RegExp(search, `gi`), replace);
276+
}
277+
253278
const resolve = (command: string) => {
254279
command = command.replace(new RegExp(`\\*CURLIB`, `g`), `$(BIN_LIB)`);
255280
command = command.replace(new RegExp(`\\$\\*`, `g`), ileObject.systemName);
256281
command = command.replace(new RegExp(`\\$<`, `g`), asPosix(ileObject.relativePath));
257282
command = command.replace(new RegExp(`\\$\\(SRCPF\\)`, `g`), qsysTempName);
258283

284+
// Additionally, we have to support Actions variables
285+
command = simpleReplace(command, `&BUILDLIB`, `$(BIN_LIB)`);
286+
command = simpleReplace(command, `&CURLIB`, `$(BIN_LIB)`);
287+
command = simpleReplace(command, `&LIBLS`, ``);
288+
command = simpleReplace(command, `&BRANCHLIB`, `$(BIN_LIB)`);
289+
290+
const pathDetail = path.parse(ileObject.relativePath || ``);
291+
292+
command = simpleReplace(command, `&RELATIVEPATH`, asPosix(ileObject.relativePath));
293+
command = simpleReplace(command, `&BASENAME`, pathDetail.base);
294+
command = simpleReplace(command, `{filename}`, pathDetail.base);
295+
command = simpleReplace(command, `&NAME`, getTrueBasename(pathDetail.name));
296+
command = simpleReplace(command, `&EXTENSION`, pathDetail.ext.startsWith(`.`) ? pathDetail.ext.substring(1) : pathDetail.ext);
297+
259298
if (ileObject.deps && ileObject.deps.length > 0) {
260299
// This piece of code adds special variables that can be used for building dependencies
261300
const uniqueObjectTypes = ileObject.deps.map(d => d.type).filter((value, index, array) => array.indexOf(value) === index);

cli/src/index.ts

Lines changed: 3 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { ImpactMarkdown } from "./builders/imd";
1111
import { allExtensions, referencesFileName } from "./extensions";
1212
import { getBranchLibraryName, getDefaultCompiles } from "./builders/environment";
1313
import { renameFiles, replaceIncludes } from './utils';
14-
import { iProject } from './builders/iProject';
1514
import { ReadFileSystem } from './readFileSystem';
1615

1716
const isCli = process.argv.length >= 2 && (process.argv[1].endsWith(`so`) || process.argv[1].endsWith(`index.js`));
@@ -40,11 +39,6 @@ async function main() {
4039
i++;
4140
break;
4241

43-
case `-i`:
44-
case `--init`:
45-
initProject(cwd);
46-
process.exit(0);
47-
4842
case `-ar`:
4943
warningOut(`Auto rename enabled. No makefile will be generated.`)
5044
cliSettings.autoRename = true;
@@ -126,11 +120,6 @@ async function main() {
126120
console.log(``);
127121
console.log(`Options specific to '-bf make':`);
128122
console.log(``);
129-
console.log(`\t-i`);
130-
console.log(`\t--init\t\tAdd default compile options to 'iproj.json' file`);
131-
console.log(`\t\t\tShould be used for project initialisation or to customize compile commands.`);
132-
console.log(`\t\t\tThis is specific to using '-bf' with the 'make' option.`);
133-
console.log(``);
134123
console.log(`\t-nc`);
135124
console.log(`\t--no-children\tUsed with '-bf make' and won't include children of`);
136125
console.log(`\t\t\tobjects in the makefile. Useful in conjuction with '-f'.`);
@@ -223,7 +212,9 @@ async function main() {
223212

224213
break;
225214
case `make`:
226-
const makeProj = new MakeProject(cwd, targets);
215+
const makeProj = new MakeProject(cwd, targets, fs);
216+
await makeProj.setupSettings();
217+
227218
makeProj.setNoChildrenInBuild(cliSettings.makeFileNoChildren);
228219

229220
let specificObjects: ILEObject[] | undefined = cliSettings.fileList ? cliSettings.lookupFiles.map(f => targets.getResolvedObject(path.join(cwd, f))).filter(o => o) : undefined;
@@ -248,34 +239,6 @@ async function main() {
248239
}
249240
}
250241

251-
function initProject(cwd) {
252-
console.log(`Initialising in ${cwd}`);
253-
254-
const iprojPath = path.join(cwd, `iproj.json`);
255-
256-
let base: Partial<iProject> = {};
257-
const iprojExists = existsSync(iprojPath);
258-
259-
if (iprojExists) {
260-
try {
261-
console.log(`iproj.json already exists. Will append new properties.`);
262-
base = JSON.parse(readFileSync(iprojPath, { encoding: `utf-8` }));
263-
} catch (e) {
264-
error(`Failed to parse iproj.json. Aborting`);
265-
process.exit(1);
266-
}
267-
}
268-
269-
base = {
270-
...base,
271-
compiles: getDefaultCompiles()
272-
};
273-
274-
writeFileSync(iprojPath, JSON.stringify(base, null, 2));
275-
276-
console.log(`Written to ${iprojPath}`);
277-
}
278-
279242
/**
280243
* @param query Can be object (ABCD.PGM) or relative path
281244
*/

cli/src/readFileSystem.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import { scanGlob } from './extensions';
99
export class ReadFileSystem {
1010
constructor() {}
1111

12-
async getFiles(cwd: string, globPath = scanGlob): Promise<string[]> {
13-
return getFiles(cwd, globPath);
12+
async getFiles(cwd: string, globPath = scanGlob, additionalOpts: any = {}): Promise<string[]> {
13+
return getFiles(cwd, globPath, additionalOpts);
1414
}
1515

1616
readFile(filePath: string): Promise<string> {

cli/src/targets.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ export class Targets {
119119
return this.cwd;
120120
}
121121

122+
get rfs() {
123+
return this.fs;
124+
}
125+
122126
public setAssumePrograms(assumePrograms: boolean) {
123127
this.assumePrograms = assumePrograms;
124128
}
@@ -312,6 +316,7 @@ export class Targets {
312316

313317
let globString = `**/${name}*`;
314318

319+
// TODO: replace with rfs.getFiles
315320
const results = glob.sync(globString, {
316321
cwd: this.cwd,
317322
absolute: true,
@@ -683,6 +688,10 @@ export class Targets {
683688
files.forEach(def => {
684689
const possibleObject = def.file;
685690
if (possibleObject) {
691+
if (possibleObject.library?.toUpperCase() === `*LIBL`) {
692+
possibleObject.library = undefined; // This means lookup as normal
693+
}
694+
686695
if (possibleObject.library) {
687696
this.logger.fileLog(ileObject.relativePath, {
688697
message: `Definition to ${possibleObject.library}/${possibleObject.name} ignored due to qualified path.`,

0 commit comments

Comments
 (0)