Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cli/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 85 additions & 0 deletions cli/src/builders/actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { ReadFileSystem } from "../../readFileSystem";
import { Targets } from "../../targets";
import path from "path";

export interface Action {
name: string,
command: string,
environment: "ile",
extensions: string[],
}

export class ProjectActions {
private actions: { [relativePath: string]: Action[] } = {};

constructor(private readonly targets: Targets, private readonly readFileSystem: ReadFileSystem) {}

get getActionPaths() {
return Object.keys(this.actions);
}

public async loadAllActions() {
const cwd = this.targets.getCwd();
const files = await this.readFileSystem.getFiles(cwd, `**/actions.json`, {dot: true});

for (const file of files) {
const relativePath = path.relative(cwd, file);
const contents = await this.readFileSystem.readFile(file);
try {
const possibleActions = JSON.parse(contents) as Partial<Action[]>;

for (const possibleAction of possibleActions) {
if (!possibleAction.name || !possibleAction.command || !possibleAction.environment || !possibleAction.extensions) {
// TODO: Log a warning about missing required fields
continue; // Skip if required fields are missing
}

possibleAction.extensions = possibleAction.extensions.map(ext => ext.toLowerCase());
}

this.actions[relativePath] = possibleActions as Action[];
} catch (e) {
console.log(`Error parsing actions.json at ${relativePath}:`, e);
}
}

const vscodePath = path.join(`.vscode`, `actions.json`);
if (this.actions[vscodePath]) {
// If there is a .vscode/actions.json, it is the project actions
this.actions[`actions.json`] = this.actions[vscodePath];
delete this.actions[vscodePath];
}
}

getActionForPath(relativeSourcePath: string): Action|undefined {
let allPossibleActions: Action[] = [];
let parent: string = relativeSourcePath;

while (true) {
parent = path.dirname(parent);

const actionFile = path.join(parent, `actions.json`);

if (this.actions[actionFile]) {
allPossibleActions.push(...this.actions[actionFile]);
}

if (parent === `.`) {
// Reached the root directory, stop searching
break;
}
}

let extension = path.extname(relativeSourcePath).toLowerCase();

if (extension.startsWith(`.`)) {
extension = extension.slice(1);
}

allPossibleActions = allPossibleActions.filter((action) => {
return action.extensions.includes(extension);
});

return allPossibleActions[0];
}
}
17 changes: 17 additions & 0 deletions cli/src/builders/environment.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { str } from "crc-32/crc32c";
import { ObjectType } from "../targets";
import path from "path";

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

export function getTrueBasename(name: string) {
// Logic to handle second extension, caused by bob.
const sourceObjectTypes = [`.PGM`, `.SRVPGM`, `.TEST`];
const secondName = path.parse(name);
if (secondName.ext && sourceObjectTypes.includes(secondName.ext.toUpperCase())) {
name = secondName.name;
}

// Remove bob text convention
if (name.includes(`-`)) {
name = name.substring(0, name.indexOf(`-`));
}

return name;
}

export function getObjectType(ext: string): ObjectType {
switch (ext.toLowerCase()) {
case `dspf`:
Expand Down
17 changes: 1 addition & 16 deletions cli/src/builders/iProject.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { fromCl } from "../utils";
import { CompileData, CommandParameters, CompileAttribute, getDefaultCompiles, Action, getObjectType } from "./environment";
import { CommandParameters, CompileAttribute, getDefaultCompiles } from "./environment";

export class iProject {
includePaths?: string[] = [];
Expand Down Expand Up @@ -33,18 +32,4 @@ export class iProject {
}
}
}

applyAction(newAction: Action) {
if (newAction.environment && newAction.environment === `ile` && newAction.extensions && newAction.extensions.length > 0) {
if (!newAction.extensions.includes(`GLOBAL`)) {
const firstExt = newAction.extensions[0].toLowerCase();
const becomesObject = getObjectType(firstExt);
const commandData = fromCl(newAction.command);
this.compiles[firstExt] = {
becomes: becomesObject,
...commandData
};
}
}
}
}
2 changes: 1 addition & 1 deletion cli/src/builders/make/customRules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as path from "path";
import { MakeProject } from ".";

/**
* Scan for all rules.mk files and read attributes and custom
* Scan for all ibmi-bob rules.mk files and read attributes and custom
* dependencies into the targets.
*/
export function readAllRules(targets: Targets, project: MakeProject) {
Expand Down
55 changes: 47 additions & 8 deletions cli/src/builders/make/index.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,37 @@
import { existsSync, readFileSync } from 'fs';
import path from 'path';
import { ILEObject, ILEObjectTarget, ImpactedObject, ObjectType, Targets } from '../../targets';
import { asPosix, getFiles, toCl } from '../../utils';
import { asPosix, fromCl, getFiles, toCl } from '../../utils';
import { warningOut } from '../../cli';
import { name } from '../../../webpack.config';
import { FolderOptions, getFolderOptions } from './folderSettings';
import { readAllRules } from './customRules';
import { CompileData, CommandParameters } from '../environment';
import { CompileData, CommandParameters, getTrueBasename } from '../environment';
import { iProject } from '../iProject';
import { ReadFileSystem } from '../../readFileSystem';
import { ProjectActions } from '../actions';

export class MakeProject {
private noChildren: boolean = false;
private settings: iProject = new iProject();
private projectActions: ProjectActions;

private folderSettings: {[folder: string]: FolderOptions} = {};

constructor(private cwd: string, private targets: Targets) {
this.setupSettings();
constructor(private cwd: string, private targets: Targets, private rfs: ReadFileSystem) {
this.projectActions = new ProjectActions(this.targets, this.rfs);
}

public setNoChildrenInBuild(noChildren: boolean) {
this.noChildren = noChildren;
}

private setupSettings() {
async setupSettings() {
await this.projectActions.loadAllActions();

// First, let's setup the project settings
try {
const content = readFileSync(path.join(this.cwd, `iproj.json`), { encoding: `utf-8` });
const content = await this.rfs.readFile(path.join(this.cwd, `iproj.json`));
const asJson: iProject = JSON.parse(content);

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

for (const entry of Object.entries(this.settings.compiles)) {
const [type, data] = entry;
let [type, data] = entry;

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

const possibleTarget: ILEObjectTarget = this.targets.getTarget(ileObject) || (ileObject as ILEObjectTarget);
const customAttributes = this.getObjectAttributes(data, possibleTarget);
let customAttributes = this.getObjectAttributes(data, possibleTarget);

if (ileObject.relativePath) {
const possibleAction = this.projectActions.getActionForPath(ileObject.relativePath);
if (possibleAction) {
const clData = fromCl(possibleAction.command);
// If there is an action for this object, we want to apply the action's parameters
// to the custom attributes.

data = {
...data,
command: clData.command,
parameters: clData.parameters
}
}
}

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

const simpleReplace = (str: string, search: string, replace: string) => {
return str.replace(new RegExp(search, `gi`), replace);
}

const resolve = (command: string) => {
command = command.replace(new RegExp(`\\*CURLIB`, `g`), `$(BIN_LIB)`);
command = command.replace(new RegExp(`\\$\\*`, `g`), ileObject.systemName);
command = command.replace(new RegExp(`\\$<`, `g`), asPosix(ileObject.relativePath));
command = command.replace(new RegExp(`\\$\\(SRCPF\\)`, `g`), qsysTempName);

// Additionally, we have to support Actions variables
command = simpleReplace(command, `&BUILDLIB`, `$(BIN_LIB)`);
command = simpleReplace(command, `&CURLIB`, `$(BIN_LIB)`);
command = simpleReplace(command, `&LIBLS`, ``);
command = simpleReplace(command, `&BRANCHLIB`, `$(BIN_LIB)`);

const pathDetail = path.parse(ileObject.relativePath || ``);

command = simpleReplace(command, `&RELATIVEPATH`, asPosix(ileObject.relativePath));
command = simpleReplace(command, `&BASENAME`, pathDetail.base);
command = simpleReplace(command, `{filename}`, pathDetail.base);
command = simpleReplace(command, `&NAME`, getTrueBasename(pathDetail.name));
command = simpleReplace(command, `&EXTENSION`, pathDetail.ext.startsWith(`.`) ? pathDetail.ext.substring(1) : pathDetail.ext);

if (ileObject.deps && ileObject.deps.length > 0) {
// This piece of code adds special variables that can be used for building dependencies
const uniqueObjectTypes = ileObject.deps.map(d => d.type).filter((value, index, array) => array.indexOf(value) === index);
Expand Down
43 changes: 3 additions & 40 deletions cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { ImpactMarkdown } from "./builders/imd";
import { allExtensions, referencesFileName } from "./extensions";
import { getBranchLibraryName, getDefaultCompiles } from "./builders/environment";
import { renameFiles, replaceIncludes } from './utils';
import { iProject } from './builders/iProject';
import { ReadFileSystem } from './readFileSystem';

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

case `-i`:
case `--init`:
initProject(cwd);
process.exit(0);

case `-ar`:
warningOut(`Auto rename enabled. No makefile will be generated.`)
cliSettings.autoRename = true;
Expand Down Expand Up @@ -126,11 +120,6 @@ async function main() {
console.log(``);
console.log(`Options specific to '-bf make':`);
console.log(``);
console.log(`\t-i`);
console.log(`\t--init\t\tAdd default compile options to 'iproj.json' file`);
console.log(`\t\t\tShould be used for project initialisation or to customize compile commands.`);
console.log(`\t\t\tThis is specific to using '-bf' with the 'make' option.`);
console.log(``);
console.log(`\t-nc`);
console.log(`\t--no-children\tUsed with '-bf make' and won't include children of`);
console.log(`\t\t\tobjects in the makefile. Useful in conjuction with '-f'.`);
Expand Down Expand Up @@ -223,7 +212,9 @@ async function main() {

break;
case `make`:
const makeProj = new MakeProject(cwd, targets);
const makeProj = new MakeProject(cwd, targets, fs);
await makeProj.setupSettings();

makeProj.setNoChildrenInBuild(cliSettings.makeFileNoChildren);

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

function initProject(cwd) {
console.log(`Initialising in ${cwd}`);

const iprojPath = path.join(cwd, `iproj.json`);

let base: Partial<iProject> = {};
const iprojExists = existsSync(iprojPath);

if (iprojExists) {
try {
console.log(`iproj.json already exists. Will append new properties.`);
base = JSON.parse(readFileSync(iprojPath, { encoding: `utf-8` }));
} catch (e) {
error(`Failed to parse iproj.json. Aborting`);
process.exit(1);
}
}

base = {
...base,
compiles: getDefaultCompiles()
};

writeFileSync(iprojPath, JSON.stringify(base, null, 2));

console.log(`Written to ${iprojPath}`);
}

/**
* @param query Can be object (ABCD.PGM) or relative path
*/
Expand Down
4 changes: 2 additions & 2 deletions cli/src/readFileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { scanGlob } from './extensions';
export class ReadFileSystem {
constructor() {}

async getFiles(cwd: string, globPath = scanGlob): Promise<string[]> {
return getFiles(cwd, globPath);
async getFiles(cwd: string, globPath = scanGlob, additionalOpts: any = {}): Promise<string[]> {
return getFiles(cwd, globPath, additionalOpts);
}

readFile(filePath: string): Promise<string> {
Expand Down
9 changes: 9 additions & 0 deletions cli/src/targets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ export class Targets {
return this.cwd;
}

get rfs() {
return this.fs;
}

public setAssumePrograms(assumePrograms: boolean) {
this.assumePrograms = assumePrograms;
}
Expand Down Expand Up @@ -312,6 +316,7 @@ export class Targets {

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

// TODO: replace with rfs.getFiles
const results = glob.sync(globString, {
cwd: this.cwd,
absolute: true,
Expand Down Expand Up @@ -683,6 +688,10 @@ export class Targets {
files.forEach(def => {
const possibleObject = def.file;
if (possibleObject) {
if (possibleObject.library?.toUpperCase() === `*LIBL`) {
possibleObject.library = undefined; // This means lookup as normal
}

if (possibleObject.library) {
this.logger.fileLog(ileObject.relativePath, {
message: `Definition to ${possibleObject.library}/${possibleObject.name} ignored due to qualified path.`,
Expand Down
Loading