From 525a482412ae58ad0f5f12b5344c084b15b0b2a1 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 30 May 2025 10:21:49 -0400 Subject: [PATCH 01/10] Initial work for actions support Signed-off-by: worksofliam --- cli/src/builders/actions/index.ts | 85 +++++++++++++++++++ cli/src/readFileSystem.ts | 4 +- cli/src/targets.ts | 1 + cli/src/utils.ts | 5 +- cli/test/cs_srvpgm.test.ts | 33 +++++++ .../fixtures/cs_srvpgm/.vscode/actions.json | 18 ++++ .../fixtures/cs_srvpgm/qddssrc/actions.json | 10 +++ 7 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 cli/src/builders/actions/index.ts create mode 100644 cli/test/fixtures/cs_srvpgm/.vscode/actions.json create mode 100644 cli/test/fixtures/cs_srvpgm/qddssrc/actions.json diff --git a/cli/src/builders/actions/index.ts b/cli/src/builders/actions/index.ts new file mode 100644 index 0000000..e121474 --- /dev/null +++ b/cli/src/builders/actions/index.ts @@ -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; + + 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 = allPossibleActions.concat(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]; + } +} \ No newline at end of file diff --git a/cli/src/readFileSystem.ts b/cli/src/readFileSystem.ts index 29de9d4..647bf8d 100644 --- a/cli/src/readFileSystem.ts +++ b/cli/src/readFileSystem.ts @@ -9,8 +9,8 @@ import { scanGlob } from './extensions'; export class ReadFileSystem { constructor() {} - async getFiles(cwd: string, globPath = scanGlob): Promise { - return getFiles(cwd, globPath); + async getFiles(cwd: string, globPath = scanGlob, additionalOpts: any = {}): Promise { + return getFiles(cwd, globPath, additionalOpts); } readFile(filePath: string): Promise { diff --git a/cli/src/targets.ts b/cli/src/targets.ts index 469675a..249e4b2 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -312,6 +312,7 @@ export class Targets { let globString = `**/${name}*`; + // TODO: replace with rfs.getFiles const results = glob.sync(globString, { cwd: this.cwd, absolute: true, diff --git a/cli/src/utils.ts b/cli/src/utils.ts index faa631b..45cf8d6 100644 --- a/cli/src/utils.ts +++ b/cli/src/utils.ts @@ -57,13 +57,14 @@ export function getSystemNameFromPath(inputName: string) { } /** - * @deprecated Use {@link ReadFileSystem.getFiles} instead + * @deprecated Use {@link ReadFileSystem} instead */ -export function getFiles(cwd: string, globPath: string): string[] { +export function getFiles(cwd: string, globPath: string, additionalOpts: any = {}): string[] { let paths: string[] = glob.sync(globPath, { cwd, absolute: true, nocase: true, + ...additionalOpts }); if (os.platform() === `win32`) { diff --git a/cli/test/cs_srvpgm.test.ts b/cli/test/cs_srvpgm.test.ts index 8311599..5c76ca6 100644 --- a/cli/test/cs_srvpgm.test.ts +++ b/cli/test/cs_srvpgm.test.ts @@ -6,6 +6,7 @@ import { setupFixture } from './fixtures/projects'; import { ReadFileSystem } from '../src/readFileSystem'; import { BobProject } from '../src/builders/bob'; import path from 'path'; +import { ProjectActions } from '../src/builders/actions'; describe(`pseudo tests`, () => { const project = setupFixture(`cs_srvpgm`); @@ -13,6 +14,7 @@ describe(`pseudo tests`, () => { const fs = new ReadFileSystem(); const targets = new Targets(project.cwd, fs); let make: MakeProject; + let actions: ProjectActions beforeAll(async () => { project.setup(); @@ -22,6 +24,9 @@ describe(`pseudo tests`, () => { targets.resolveBinder(); make = new MakeProject(project.cwd, targets); + + actions = new ProjectActions(targets, fs); + await actions.loadAllActions(); }); test(`That test files are understood`, () => { @@ -81,4 +86,32 @@ describe(`pseudo tests`, () => { expect(contents).not.toContain(`EMPDET.SRVPGM`); // Ensure no service program is created expect(contents).toContain(`EMPDET.MODULE`); }); + + test('there are actions', async () => { + expect(actions.getActionPaths.length).toBe(2); + expect(actions.getActionPaths).toContain(`actions.json`); + expect(actions.getActionPaths).toContain(`qddssrc/actions.json`); + }); + + test('correct actions get detected', async () => { + const ddsSrcA = path.join(`qddssrc`, `popemp.sql`); + const ddsSrcB = path.join(`qsqlsrc`, `popemp.sql`); + + // Finds the actions file `qddssrc` + const actionA = actions.getActionForPath(ddsSrcA); + + // Finds the project actions file + const actionB = actions.getActionForPath(ddsSrcB); + + expect(actionA).toBeDefined(); + expect(actionA?.command).toBe(`RUNSQLSTM`); + + expect(actionB).toBeDefined(); + expect(actionB?.command).toBe(`RUNSQL`); + + const rpgleSrc = path.join(`qrpglesrc`, `mypgm.pgm.rpgle`); + const actionC = actions.getActionForPath(rpgleSrc); + expect(actionC).toBeDefined(); + expect(actionC?.command).toBe(`CRTBNDRPG`); + }); }); diff --git a/cli/test/fixtures/cs_srvpgm/.vscode/actions.json b/cli/test/fixtures/cs_srvpgm/.vscode/actions.json new file mode 100644 index 0000000..f7678ae --- /dev/null +++ b/cli/test/fixtures/cs_srvpgm/.vscode/actions.json @@ -0,0 +1,18 @@ +[ + { + "name": "Create an RPG program", + "environment": "ile", + "extensions": [ + "rpgle" + ], + "command": "CRTBNDRPG" + }, + { + "name": "Global run SQL", + "environment": "ile", + "extensions": [ + "sql" + ], + "command": "RUNSQL" + } +] \ No newline at end of file diff --git a/cli/test/fixtures/cs_srvpgm/qddssrc/actions.json b/cli/test/fixtures/cs_srvpgm/qddssrc/actions.json new file mode 100644 index 0000000..019cce9 --- /dev/null +++ b/cli/test/fixtures/cs_srvpgm/qddssrc/actions.json @@ -0,0 +1,10 @@ +[ + { + "name": "Run SQL stuff", + "environment": "ile", + "extensions": [ + "sql", "table" + ], + "command": "RUNSQLSTM" + } +] \ No newline at end of file From 8cd1ec8f6a1bb9958e55c9953365bc64f0b0f372 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 30 May 2025 10:57:17 -0400 Subject: [PATCH 02/10] For make projects, load up custom rules from actions file Signed-off-by: worksofliam --- cli/src/builders/iProject.ts | 14 ----------- cli/src/builders/make/customRules.ts | 2 +- cli/src/builders/make/index.ts | 37 ++++++++++++++++++++++------ cli/src/index.ts | 4 ++- cli/test/cs_srvpgm.test.ts | 8 +++--- cli/test/cs_with_bnddir.test.ts | 8 +++--- cli/test/ddsDeps.test.ts | 5 ++-- cli/test/ddsDepsWithRefFile.test.ts | 5 ++-- cli/test/make.test.ts | 16 ++++++++---- cli/test/multiModule.test.ts | 5 ++-- cli/test/multiModule2.test.ts | 3 ++- cli/test/project.test.ts | 31 ++++++++++++++--------- cli/test/project2.test.ts | 31 ++++++++++++++--------- cli/test/pseudo.test.ts | 3 ++- 14 files changed, 106 insertions(+), 66 deletions(-) diff --git a/cli/src/builders/iProject.ts b/cli/src/builders/iProject.ts index c48c930..3a6fab8 100644 --- a/cli/src/builders/iProject.ts +++ b/cli/src/builders/iProject.ts @@ -33,18 +33,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 - }; - } - } - } } \ No newline at end of file diff --git a/cli/src/builders/make/customRules.ts b/cli/src/builders/make/customRules.ts index e960601..23e2133 100644 --- a/cli/src/builders/make/customRules.ts +++ b/cli/src/builders/make/customRules.ts @@ -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) { diff --git a/cli/src/builders/make/index.ts b/cli/src/builders/make/index.ts index d6b7f1c..f6cd4d0 100644 --- a/cli/src/builders/make/index.ts +++ b/cli/src/builders/make/index.ts @@ -1,31 +1,35 @@ 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 { iProject } from '../iProject'; +import { ReadFileSystem } from '../../readFileSystem'; +import { ProjectActions } from '../actions'; export class MakeProject { private noChildren: boolean = false; private settings: iProject = new iProject(); + private actions: 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.actions = new ProjectActions(this.targets, this.rfs); } public setNoChildrenInBuild(noChildren: boolean) { this.noChildren = noChildren; } - private setupSettings() { + async setupSettings() { // 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); @@ -37,6 +41,8 @@ export class MakeProject { this.folderSettings = getFolderOptions(this.cwd); readAllRules(this.targets, this); + + await this.actions.loadAllActions(); } public getSettings() { @@ -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) { @@ -213,7 +219,24 @@ 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.actions.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: { + ...data.parameters, + } + } + } + } lines.push(...MakeProject.generateSpecificTarget(data, possibleTarget, customAttributes)); } diff --git a/cli/src/index.ts b/cli/src/index.ts index 8b7aec4..af81c10 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -223,7 +223,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; diff --git a/cli/test/cs_srvpgm.test.ts b/cli/test/cs_srvpgm.test.ts index 5c76ca6..273b2de 100644 --- a/cli/test/cs_srvpgm.test.ts +++ b/cli/test/cs_srvpgm.test.ts @@ -23,7 +23,8 @@ describe(`pseudo tests`, () => { expect(targets.getTargets().length).toBeGreaterThan(0); targets.resolveBinder(); - make = new MakeProject(project.cwd, targets); + make = new MakeProject(project.cwd, targets, fs); + await make.setupSettings(); actions = new ProjectActions(targets, fs); await actions.loadAllActions(); @@ -75,8 +76,9 @@ describe(`pseudo tests`, () => { expect(files[path.join(`qrpglesrc`, `Rules.mk`)]).not.toContain(`EMPDET.SRVPGM`); // Ensure no service program is created }); - test('makefile', () => { - const makefile = new MakeProject(targets.getCwd(), targets); + test('makefile', async () => { + const makefile = new MakeProject(targets.getCwd(), targets, fs); + await make.setupSettings(); const contents = makefile.getMakefile().join(`\n`); diff --git a/cli/test/cs_with_bnddir.test.ts b/cli/test/cs_with_bnddir.test.ts index fa88496..590b6a5 100644 --- a/cli/test/cs_with_bnddir.test.ts +++ b/cli/test/cs_with_bnddir.test.ts @@ -21,7 +21,8 @@ describe(`pseudo tests`, () => { expect(targets.getTargets().length).toBeGreaterThan(0); targets.resolveBinder(); - make = new MakeProject(project.cwd, targets); + make = new MakeProject(project.cwd, targets, fs); + await make.setupSettings(); }); test(`That test files are understood`, () => { @@ -54,8 +55,9 @@ describe(`pseudo tests`, () => { expect(employees.deps.find(f => f.systemName === `EMPLOYEE` && f.type === `FILE`)).toBeDefined(); }); - test('makefile', () => { - const makefile = new MakeProject(targets.getCwd(), targets); + test('makefile', async () => { + const makefile = new MakeProject(targets.getCwd(), targets, fs); + await make.setupSettings(); const contents = makefile.getMakefile().join(`\n`); diff --git a/cli/test/ddsDeps.test.ts b/cli/test/ddsDeps.test.ts index 44013c0..777f307 100644 --- a/cli/test/ddsDeps.test.ts +++ b/cli/test/ddsDeps.test.ts @@ -80,8 +80,9 @@ describe(`dds_refs tests`, () => { expect(logs).toBeUndefined(); }); - test(`make doesn't include refs that do not exist`, () => { - const makeProject = new MakeProject(project.cwd, targets); + test(`make doesn't include refs that do not exist`, async () => { + const makeProject = new MakeProject(project.cwd, targets, fs); + await makeProject.setupSettings(); const targetContent = makeProject.generateTargets(); diff --git a/cli/test/ddsDepsWithRefFile.test.ts b/cli/test/ddsDepsWithRefFile.test.ts index 4157a38..33c41e3 100644 --- a/cli/test/ddsDepsWithRefFile.test.ts +++ b/cli/test/ddsDepsWithRefFile.test.ts @@ -84,8 +84,9 @@ describe(`dds_refs tests with reference file`, () => { }); - test(`make doesn't include refs that do not exist or are referenced objects`, () => { - const makeProject = new MakeProject(project.cwd, targets); + test(`make doesn't include refs that do not exist or are referenced objects`, async () => { + const makeProject = new MakeProject(project.cwd, targets, fs); + await makeProject.setupSettings(); const targetContent = makeProject.generateTargets(); diff --git a/cli/test/make.test.ts b/cli/test/make.test.ts index 0274755..c6869f9 100644 --- a/cli/test/make.test.ts +++ b/cli/test/make.test.ts @@ -1,10 +1,12 @@ import { assert, expect, test } from 'vitest' import { baseTargets, cwd, multiModuleObjects } from './fixtures/targets'; import { MakeProject } from '../src/builders/make'; +import { ReadFileSystem } from '../src/readFileSystem'; test('generateTargets (pre-resolve)', async () => { const targets = await baseTargets(true); - const project = new MakeProject(cwd, targets); + const project = new MakeProject(cwd, targets, new ReadFileSystem()); + await project.setupSettings(); const targetContent = project.generateTargets(); @@ -32,7 +34,7 @@ test('generateTargets (post-resolve)', async () => { targets.resolveBinder(); - const project = new MakeProject(cwd, targets); + const project = new MakeProject(cwd, targets, new ReadFileSystem()); const targetContent = project.generateTargets(); @@ -62,7 +64,8 @@ test('generateTargets (post-resolve)', async () => { test('generateHeader (binder changes)', async () => { const targets = await baseTargets(true); - const project = new MakeProject(cwd, targets); + const project = new MakeProject(cwd, targets, new ReadFileSystem()); + await project.setupSettings(); const headerContentA = project.generateHeader(); let bndDirIndex = headerContentA.findIndex(h => h.startsWith(`BNDDIR=`)); @@ -80,7 +83,8 @@ test('generateHeader (binder changes)', async () => { test('applySettings (binder)', async () => { const targets = await baseTargets(true); - const project = new MakeProject(cwd, targets); + const project = new MakeProject(cwd, targets, new ReadFileSystem()); + await project.setupSettings(); project.getSettings().applySettings({ binders: [`TESTING`] @@ -102,7 +106,9 @@ test('applySettings (binder)', async () => { test(`Multi-module program and service program`, async () => { const targets = await multiModuleObjects(); - const project = new MakeProject(cwd, targets); + const project = new MakeProject(cwd, targets, new ReadFileSystem()); + await project.setupSettings(); + const settings = project.getSettings(); settings.applySettings({ diff --git a/cli/test/multiModule.test.ts b/cli/test/multiModule.test.ts index fd85c6c..a661847 100644 --- a/cli/test/multiModule.test.ts +++ b/cli/test/multiModule.test.ts @@ -48,8 +48,9 @@ describe(`multi_module tests`, () => { expect(objectsMod.deps.length).toBe(0); }); - test(`Generate makefile`, () => { - const makeProj = new MakeProject(project.cwd, targets); + test(`Generate makefile`, async () => { + const makeProj = new MakeProject(project.cwd, targets, fs); + await makeProj.setupSettings(); writeFileSync(path.join(project.cwd, `makefile`), makeProj.getMakefile().join(`\n`)); }); diff --git a/cli/test/multiModule2.test.ts b/cli/test/multiModule2.test.ts index a111ee6..0a41fc9 100644 --- a/cli/test/multiModule2.test.ts +++ b/cli/test/multiModule2.test.ts @@ -53,7 +53,8 @@ describe(`multi_module_two tests`, () => { }); test(`Check makefile result`, async () => { - const makeProject = new MakeProject(project.cwd, targets); + const makeProject = new MakeProject(project.cwd, targets, fs); + await makeProject.setupSettings(); const targetContent = makeProject.getMakefile(); diff --git a/cli/test/project.test.ts b/cli/test/project.test.ts index 7186602..e43b837 100644 --- a/cli/test/project.test.ts +++ b/cli/test/project.test.ts @@ -272,8 +272,9 @@ describe(`company_system tests`, () => { ].join()); }); - test(`Checking makefile rule generation`, () => { - const makeProject = new MakeProject(project.cwd, targets); + test(`Checking makefile rule generation`, async () => { + const makeProject = new MakeProject(project.cwd, targets, fs); + await makeProject.setupSettings(); const headerContent = makeProject.generateGenericRules(); @@ -283,8 +284,9 @@ describe(`company_system tests`, () => { expect(headerContent.find(l => l === `$(PREPATH)/DEPARTMENT.FILE: qddssrc/department.table`)).toBeDefined(); }); - test(`Makefile targets for all`, () => { - const makeProject = new MakeProject(project.cwd, targets); + test(`Makefile targets for all`, async () => { + const makeProject = new MakeProject(project.cwd, targets, fs); + await makeProject.setupSettings(); const header = makeProject.generateHeader(); expect(header).toContain(`APP_BNDDIR=$(APP_BNDDIR)`); @@ -303,8 +305,9 @@ describe(`company_system tests`, () => { expect(allTarget).toContain(`$(PREPATH)/EMPLOYEES.PGM`); }); - test(`Makefile targets for partial build (DEPTS display file)`, () => { - const makeProject = new MakeProject(project.cwd, targets); + test(`Makefile targets for partial build (DEPTS display file)`, async () => { + const makeProject = new MakeProject(project.cwd, targets, fs); + await makeProject.setupSettings(); const deptsFile = targets.getTarget({systemName: `DEPTS`, type: `FILE`}); @@ -317,8 +320,9 @@ describe(`company_system tests`, () => { expect(allTarget).toBe(`all: .logs .evfevent library $(PREPATH)/DEPTS.FILE $(PREPATH)/DEPTS.PGM`); }); - test(`Makefile targets for partial build (EMPLOYEE table)`, () => { - const makeProject = new MakeProject(project.cwd, targets); + test(`Makefile targets for partial build (EMPLOYEE table)`, async () => { + const makeProject = new MakeProject(project.cwd, targets, fs); + await makeProject.setupSettings(); const deptsFile = targets.getTarget({systemName: `EMPLOYEE`, type: `FILE`}); @@ -345,8 +349,10 @@ describe(`company_system tests`, () => { expect(deptsTargetDeps).toContain(`$(PREPATH)/DEPARTMENT.FILE`); }); - test(`Makefile targets for partial build (EMPLOYEE table) without children`, () => { - const makeProject = new MakeProject(project.cwd, targets); + test(`Makefile targets for partial build (EMPLOYEE table) without children`, async () => { + const makeProject = new MakeProject(project.cwd, targets, fs); + await makeProject.setupSettings(); + makeProject.setNoChildrenInBuild(true); const deptsFile = targets.getTarget({systemName: `EMPLOYEE`, type: `FILE`}); @@ -447,8 +453,9 @@ describe(`company_system tests`, () => { expect(functionMake[3]).toBe(`\tsystem "RUNSQLSTM SRCSTMF('qsqlsrc/getTotalSalary.sqludf') COMMIT(*NONE)" > .logs/gettotsal.splf`); }) - test(`Generate makefile`, () => { - const makeProj = new MakeProject(project.cwd, targets); + test(`Generate makefile`, async () => { + const makeProj = new MakeProject(project.cwd, targets, fs); + await makeProj.setupSettings(); writeFileSync(path.join(project.cwd, `makefile`), makeProj.getMakefile().join(`\n`)); }); diff --git a/cli/test/project2.test.ts b/cli/test/project2.test.ts index 081aa63..16d7443 100644 --- a/cli/test/project2.test.ts +++ b/cli/test/project2.test.ts @@ -268,8 +268,9 @@ describe(`company_system tests`, () => { ].join()); }); - test(`Checking makefile rule generation`, () => { - const makeProject = new MakeProject(project.cwd, targets); + test(`Checking makefile rule generation`, async () => { + const makeProject = new MakeProject(project.cwd, targets, fs); + await makeProject.setupSettings(); const headerContent = makeProject.generateGenericRules(); @@ -279,8 +280,9 @@ describe(`company_system tests`, () => { expect(headerContent.find(l => l === `$(PREPATH)/DEPARTMENT.FILE: qddssrc/department.table`)).toBeDefined(); }); - test(`Makefile targets for all`, () => { - const makeProject = new MakeProject(project.cwd, targets); + test(`Makefile targets for all`, async () => { + const makeProject = new MakeProject(project.cwd, targets, fs); + await makeProject.setupSettings(); // Generate targets on it's own will have BNDDIR, PGM, etc const headerContent = makeProject.generateTargets(); @@ -296,8 +298,9 @@ describe(`company_system tests`, () => { expect(allTarget).toContain(`$(PREPATH)/EMPLOYEES.PGM`); }); - test(`Makefile targets for partial build (DEPTS display file)`, () => { - const makeProject = new MakeProject(project.cwd, targets); + test(`Makefile targets for partial build (DEPTS display file)`, async () => { + const makeProject = new MakeProject(project.cwd, targets, fs); + await makeProject.setupSettings(); const deptsFile = targets.getTarget({systemName: `DEPTS`, type: `FILE`}); @@ -310,8 +313,9 @@ describe(`company_system tests`, () => { expect(allTarget).toBe(`all: .logs .evfevent library $(PREPATH)/DEPTS.FILE $(PREPATH)/DEPTS.PGM`); }); - test(`Makefile targets for partial build (EMPLOYEE table)`, () => { - const makeProject = new MakeProject(project.cwd, targets); + test(`Makefile targets for partial build (EMPLOYEE table)`, async () => { + const makeProject = new MakeProject(project.cwd, targets, fs); + await makeProject.setupSettings(); const deptsFile = targets.getTarget({systemName: `EMPLOYEE`, type: `FILE`}); @@ -338,8 +342,10 @@ describe(`company_system tests`, () => { expect(deptsTargetDeps).toContain(`$(PREPATH)/DEPARTMENT.FILE`); }); - test(`Makefile targets for partial build (EMPLOYEE table) without children`, () => { - const makeProject = new MakeProject(project.cwd, targets); + test(`Makefile targets for partial build (EMPLOYEE table) without children`, async () => { + const makeProject = new MakeProject(project.cwd, targets, fs); + await makeProject.setupSettings(); + makeProject.setNoChildrenInBuild(true); const deptsFile = targets.getTarget({systemName: `EMPLOYEE`, type: `FILE`}); @@ -440,8 +446,9 @@ describe(`company_system tests`, () => { expect(functionMake[3]).toBe(`\tsystem "RUNSQLSTM SRCSTMF('qsqlsrc/getTotalSalary.sqludf') COMMIT(*NONE)" > .logs/gettotsal.splf`); }) - test(`Generate makefile`, () => { - const makeProj = new MakeProject(project.cwd, targets); + test(`Generate makefile`, async () => { + const makeProj = new MakeProject(project.cwd, targets, fs); + await makeProj.setupSettings(); writeFileSync(path.join(project.cwd, `makefile`), makeProj.getMakefile().join(`\n`)); }); diff --git a/cli/test/pseudo.test.ts b/cli/test/pseudo.test.ts index 2435cb9..46d10d2 100644 --- a/cli/test/pseudo.test.ts +++ b/cli/test/pseudo.test.ts @@ -19,7 +19,8 @@ describe(`pseudo tests`, () => { expect(targets.getTargets().length).toBeGreaterThan(0); targets.resolveBinder(); - make = new MakeProject(project.cwd, targets); + make = new MakeProject(project.cwd, targets, fs); + await make.setupSettings(); }); test(`Test objects exists`, () => { From 2cde8fb0e13734e00c2aed11cda10ea47f1f8082 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 30 May 2025 11:57:01 -0400 Subject: [PATCH 03/10] Correct pass along actions commands Signed-off-by: worksofliam --- cli/src/builders/actions/index.ts | 2 +- cli/src/builders/iProject.ts | 3 +-- cli/src/builders/make/index.ts | 14 ++++++-------- cli/test/cs_srvpgm.test.ts | 11 +++++++---- cli/test/fixtures/cs_srvpgm/.vscode/actions.json | 4 ++-- cli/test/fixtures/cs_srvpgm/qddssrc/actions.json | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/cli/src/builders/actions/index.ts b/cli/src/builders/actions/index.ts index e121474..695e47b 100644 --- a/cli/src/builders/actions/index.ts +++ b/cli/src/builders/actions/index.ts @@ -61,7 +61,7 @@ export class ProjectActions { const actionFile = path.join(parent, `actions.json`); if (this.actions[actionFile]) { - allPossibleActions = allPossibleActions.concat(this.actions[actionFile]); + allPossibleActions.push(...this.actions[actionFile]); } if (parent === `.`) { diff --git a/cli/src/builders/iProject.ts b/cli/src/builders/iProject.ts index 3a6fab8..d17d547 100644 --- a/cli/src/builders/iProject.ts +++ b/cli/src/builders/iProject.ts @@ -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[] = []; diff --git a/cli/src/builders/make/index.ts b/cli/src/builders/make/index.ts index f6cd4d0..62fd5db 100644 --- a/cli/src/builders/make/index.ts +++ b/cli/src/builders/make/index.ts @@ -14,12 +14,12 @@ import { ProjectActions } from '../actions'; export class MakeProject { private noChildren: boolean = false; private settings: iProject = new iProject(); - private actions: ProjectActions; + private projectActions: ProjectActions; private folderSettings: {[folder: string]: FolderOptions} = {}; constructor(private cwd: string, private targets: Targets, private rfs: ReadFileSystem) { - this.actions = new ProjectActions(this.targets, this.rfs); + this.projectActions = new ProjectActions(this.targets, this.rfs); } public setNoChildrenInBuild(noChildren: boolean) { @@ -27,6 +27,8 @@ export class MakeProject { } async setupSettings() { + await this.projectActions.loadAllActions(); + // First, let's setup the project settings try { const content = await this.rfs.readFile(path.join(this.cwd, `iproj.json`)); @@ -41,8 +43,6 @@ export class MakeProject { this.folderSettings = getFolderOptions(this.cwd); readAllRules(this.targets, this); - - await this.actions.loadAllActions(); } public getSettings() { @@ -222,7 +222,7 @@ export class MakeProject { let customAttributes = this.getObjectAttributes(data, possibleTarget); if (ileObject.relativePath) { - const possibleAction = this.actions.getActionForPath(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 @@ -231,9 +231,7 @@ export class MakeProject { data = { ...data, command: clData.command, - parameters: { - ...data.parameters, - } + parameters: clData.parameters } } } diff --git a/cli/test/cs_srvpgm.test.ts b/cli/test/cs_srvpgm.test.ts index 273b2de..96e46f3 100644 --- a/cli/test/cs_srvpgm.test.ts +++ b/cli/test/cs_srvpgm.test.ts @@ -78,7 +78,7 @@ describe(`pseudo tests`, () => { test('makefile', async () => { const makefile = new MakeProject(targets.getCwd(), targets, fs); - await make.setupSettings(); + await makefile.setupSettings(); const contents = makefile.getMakefile().join(`\n`); @@ -87,6 +87,9 @@ describe(`pseudo tests`, () => { expect(contents).not.toContain(`EMPDET.SRVPGM`); // Ensure no service program is created expect(contents).toContain(`EMPDET.MODULE`); + + // As picked up from the actions.json + expect(contents).toContain(`system "CRTBNDRPG THEPARM('''cooldude''')" > .logs/mypgm.splf`); }); test('there are actions', async () => { @@ -106,14 +109,14 @@ describe(`pseudo tests`, () => { const actionB = actions.getActionForPath(ddsSrcB); expect(actionA).toBeDefined(); - expect(actionA?.command).toBe(`RUNSQLSTM`); + expect(actionA?.command).toBe(`RUNSQLSTM SRCSTMF('&RELATIVEPATH')`); expect(actionB).toBeDefined(); - expect(actionB?.command).toBe(`RUNSQL`); + expect(actionB?.command).toBe(`RUNSQL SRCSTMF('&RELATIVEPATH')`); const rpgleSrc = path.join(`qrpglesrc`, `mypgm.pgm.rpgle`); const actionC = actions.getActionForPath(rpgleSrc); expect(actionC).toBeDefined(); - expect(actionC?.command).toBe(`CRTBNDRPG`); + expect(actionC?.command).toBe(`CRTBNDRPG THEPARM('cooldude')`); }); }); diff --git a/cli/test/fixtures/cs_srvpgm/.vscode/actions.json b/cli/test/fixtures/cs_srvpgm/.vscode/actions.json index f7678ae..4f09017 100644 --- a/cli/test/fixtures/cs_srvpgm/.vscode/actions.json +++ b/cli/test/fixtures/cs_srvpgm/.vscode/actions.json @@ -5,7 +5,7 @@ "extensions": [ "rpgle" ], - "command": "CRTBNDRPG" + "command": "CRTBNDRPG THEPARM('cooldude')" }, { "name": "Global run SQL", @@ -13,6 +13,6 @@ "extensions": [ "sql" ], - "command": "RUNSQL" + "command": "RUNSQL SRCSTMF('&RELATIVEPATH')" } ] \ No newline at end of file diff --git a/cli/test/fixtures/cs_srvpgm/qddssrc/actions.json b/cli/test/fixtures/cs_srvpgm/qddssrc/actions.json index 019cce9..4f001d2 100644 --- a/cli/test/fixtures/cs_srvpgm/qddssrc/actions.json +++ b/cli/test/fixtures/cs_srvpgm/qddssrc/actions.json @@ -5,6 +5,6 @@ "extensions": [ "sql", "table" ], - "command": "RUNSQLSTM" + "command": "RUNSQLSTM SRCSTMF('&RELATIVEPATH')" } ] \ No newline at end of file From 4d8cbde0aaa26510224fd3c5e61f67589ac903f0 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 30 May 2025 12:49:31 -0400 Subject: [PATCH 04/10] makefile actions now support action variables Signed-off-by: worksofliam --- cli/src/builders/environment.ts | 17 ++++++++++++++++ cli/src/builders/make/index.ts | 20 ++++++++++++++++++- cli/test/cs_srvpgm.test.ts | 6 ++++-- .../fixtures/cs_srvpgm/.vscode/actions.json | 2 +- 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/cli/src/builders/environment.ts b/cli/src/builders/environment.ts index fb20926..27a92f5 100644 --- a/cli/src/builders/environment.ts +++ b/cli/src/builders/environment.ts @@ -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 }; @@ -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`: diff --git a/cli/src/builders/make/index.ts b/cli/src/builders/make/index.ts index 62fd5db..fcb1d82 100644 --- a/cli/src/builders/make/index.ts +++ b/cli/src/builders/make/index.ts @@ -6,7 +6,7 @@ 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'; @@ -271,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); diff --git a/cli/test/cs_srvpgm.test.ts b/cli/test/cs_srvpgm.test.ts index 96e46f3..dbf2fc9 100644 --- a/cli/test/cs_srvpgm.test.ts +++ b/cli/test/cs_srvpgm.test.ts @@ -88,8 +88,10 @@ describe(`pseudo tests`, () => { expect(contents).not.toContain(`EMPDET.SRVPGM`); // Ensure no service program is created expect(contents).toContain(`EMPDET.MODULE`); + console.log(contents); + // As picked up from the actions.json - expect(contents).toContain(`system "CRTBNDRPG THEPARM('''cooldude''')" > .logs/mypgm.splf`); + expect(contents).toContain(`system "CRTBNDRPG NAME(mypgm) THEPARM('qrpglesrc/mypgm.pgm.rpgle')" > .logs/mypgm.splf`); }); test('there are actions', async () => { @@ -117,6 +119,6 @@ describe(`pseudo tests`, () => { const rpgleSrc = path.join(`qrpglesrc`, `mypgm.pgm.rpgle`); const actionC = actions.getActionForPath(rpgleSrc); expect(actionC).toBeDefined(); - expect(actionC?.command).toBe(`CRTBNDRPG THEPARM('cooldude')`); + expect(actionC?.command).toBe(`CRTBNDRPG NAME(&NAME) THEPARM('&RELATIVEPATH')`); }); }); diff --git a/cli/test/fixtures/cs_srvpgm/.vscode/actions.json b/cli/test/fixtures/cs_srvpgm/.vscode/actions.json index 4f09017..3aa46a1 100644 --- a/cli/test/fixtures/cs_srvpgm/.vscode/actions.json +++ b/cli/test/fixtures/cs_srvpgm/.vscode/actions.json @@ -5,7 +5,7 @@ "extensions": [ "rpgle" ], - "command": "CRTBNDRPG THEPARM('cooldude')" + "command": "CRTBNDRPG NAME(&NAME) THEPARM('&RELATIVEPATH')" }, { "name": "Global run SQL", From 968c246f16eac27942b39119a8d2c6c279e2eac0 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 30 May 2025 12:53:47 -0400 Subject: [PATCH 05/10] Exetension will take makefile actions into consideration Signed-off-by: worksofliam --- cli/package-lock.json | 4 ++-- cli/src/targets.ts | 4 ++++ vs/client/package-lock.json | 2 +- vs/package-lock.json | 4 ++-- vs/server/package-lock.json | 2 +- vs/server/src/setup.ts | 4 +++- 6 files changed, 13 insertions(+), 7 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index 7ae69c5..9b248c6 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ibm/sourceorbit", - "version": "1.0.2", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ibm/sourceorbit", - "version": "1.0.2", + "version": "1.1.0", "license": "Apache 2", "dependencies": { "crc-32": "https://cdn.sheetjs.com/crc-32-latest/crc-32-latest.tgz" diff --git a/cli/src/targets.ts b/cli/src/targets.ts index 249e4b2..0806a36 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -119,6 +119,10 @@ export class Targets { return this.cwd; } + get rfs() { + return this.fs; + } + public setAssumePrograms(assumePrograms: boolean) { this.assumePrograms = assumePrograms; } diff --git a/vs/client/package-lock.json b/vs/client/package-lock.json index f0a6cf3..9d9c0bd 100644 --- a/vs/client/package-lock.json +++ b/vs/client/package-lock.json @@ -24,7 +24,7 @@ }, "../../cli": { "name": "@ibm/sourceorbit", - "version": "1.0.4", + "version": "1.1.0", "dev": true, "license": "Apache 2", "dependencies": { diff --git a/vs/package-lock.json b/vs/package-lock.json index 9909062..b077f35 100644 --- a/vs/package-lock.json +++ b/vs/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-sourceorbit", - "version": "1.0.2", + "version": "1.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-sourceorbit", - "version": "1.0.2", + "version": "1.1.0", "hasInstallScript": true, "license": "MIT", "devDependencies": { diff --git a/vs/server/package-lock.json b/vs/server/package-lock.json index 62365b7..343abc2 100644 --- a/vs/server/package-lock.json +++ b/vs/server/package-lock.json @@ -24,7 +24,7 @@ }, "../../cli": { "name": "@ibm/sourceorbit", - "version": "1.0.4", + "version": "1.1.0", "license": "Apache 2", "dependencies": { "crc-32": "https://cdn.sheetjs.com/crc-32-latest/crc-32-latest.tgz" diff --git a/vs/server/src/setup.ts b/vs/server/src/setup.ts index a1cfc03..9a8c4c1 100644 --- a/vs/server/src/setup.ts +++ b/vs/server/src/setup.ts @@ -51,7 +51,9 @@ export async function generateBuildFile(workspaceUri: string, type: string) { break; case `make`: - const makeProj = new MakeProject(cwd, targets); + const makeProj = new MakeProject(cwd, targets, targets.rfs); + await makeProj.setupSettings(); + fs.writeFileSync(path.join(cwd, `makefile`), makeProj.getMakefile().join(`\n`)); break; From 8856fd8bade5efa8f0f16458d6923761641ed749 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 30 May 2025 12:55:30 -0400 Subject: [PATCH 06/10] Remove --init from CLI Signed-off-by: worksofliam --- cli/src/index.ts | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/cli/src/index.ts b/cli/src/index.ts index af81c10..7a5776e 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -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`)); @@ -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; @@ -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'.`); @@ -250,34 +239,6 @@ async function main() { } } -function initProject(cwd) { - console.log(`Initialising in ${cwd}`); - - const iprojPath = path.join(cwd, `iproj.json`); - - let base: Partial = {}; - 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 */ From a124305629ad44dd5cd3e5440c28931d0991fa68 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 30 May 2025 13:07:29 -0400 Subject: [PATCH 07/10] Update to docs on `actions.json` support Signed-off-by: worksofliam --- docs/pages/cli/make.md | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/docs/pages/cli/make.md b/docs/pages/cli/make.md index 07ed6b6..ac564ef 100644 --- a/docs/pages/cli/make.md +++ b/docs/pages/cli/make.md @@ -26,7 +26,7 @@ So that means that 4 objects are going to be rebuilt. Usually, parents always ne ### When is a incremental build right? -It is always recommended to do a incremental build when working in a new branch. Ensure that you have a library of objects from a previous full build on the library list! +It is always recommended to do a incremental build when working in a new branch. Ensure that you have a library of objects from a previous full build on the library list. ## `iproj.json` properties @@ -43,6 +43,39 @@ If you are making use of service programs, Source Orbit will automatically maint ## Changing compile options +When you use `so -bf make`, Source Orbit will generate a makefile that contains the compile commands for each object. The compile commands can come from two places + +### Setting up generic compiles (`actions.json`) + +The `actions.json` standard determines how files that match specific extensions are compiled. The file is typically created by a user and is located in `.vscode/actions.json`. The `actions.json` standard comes from Code for IBM i and is now used by Source Orbit. + +Check out how to use the `actions.json` file in the [Code for IBM i documentation](https://codefori.github.io/docs/developing/local/actions/). + +#### Multiple levels of actions + +The `.vscode/actions.json` is the default location for the `actions.json` file. However, you can also create a `actions.json` file in a specific directory, which will be merged with the default one. This allows you to have directory specific actions, while still using the defaults. + +``` +.vscode: + actions.json ## Actions defined here apply to the entire project + +qddssrc: + actions.json ## Actions defined here only apply to qddssrc + department.table + employee.table + emps.dspf + nemp.dspf + +qrpglesrc: + depts.pgm.sqlrpgle + empdet.sqlrpgle + employees.pgm.sqlrpgle + mypgm.pgm.rpgle + newemp.pgm.sqlrpgle +``` + +### Changing project wide compiles (`iproj.json`) + The `iproj.json` can contain a `compiles` property to customize how each extension type gets compiled. To generate the defaults, you can use `so --init` to create or update the `iproj.json` file. When you define an extension in the `compiles` property, the properties of it will override the `so` defaults. Here is the schema for the compiles option: @@ -68,7 +101,7 @@ interface CompileData { }; ``` -### Example: build object from commands +#### Example: build object from commands Objects like data areas do not have source. In the `compiles` property, there is a flag that can be used to tell Source Orbit to get the commands from the source. From 81008666d19ea48764827ebfdf68fe9128010af3 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 30 May 2025 13:09:31 -0400 Subject: [PATCH 08/10] Update hard coded path Signed-off-by: worksofliam --- cli/test/cs_srvpgm.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/test/cs_srvpgm.test.ts b/cli/test/cs_srvpgm.test.ts index dbf2fc9..c9c9409 100644 --- a/cli/test/cs_srvpgm.test.ts +++ b/cli/test/cs_srvpgm.test.ts @@ -97,7 +97,7 @@ describe(`pseudo tests`, () => { test('there are actions', async () => { expect(actions.getActionPaths.length).toBe(2); expect(actions.getActionPaths).toContain(`actions.json`); - expect(actions.getActionPaths).toContain(`qddssrc/actions.json`); + expect(actions.getActionPaths).toContain(path.join(`qddssrc`, `actions.json`)); }); test('correct actions get detected', async () => { From de8f7348c8fda8ef4e03eb635c6bdbbe5a211517 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 30 May 2025 13:11:52 -0400 Subject: [PATCH 09/10] Add note about bnddir for make Signed-off-by: worksofliam --- docs/pages/cli/make.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/pages/cli/make.md b/docs/pages/cli/make.md index ac564ef..b78373d 100644 --- a/docs/pages/cli/make.md +++ b/docs/pages/cli/make.md @@ -34,13 +34,14 @@ Source Orbit makes use of a few properties that you can put inside your `iproj.j | Property | Type | | -------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `binders` | Array of strings. Use this if you want to use export functions/procedures from other service programs outside of your repository | | `includePaths` | Array of strings. Use this if you want to include headers that can be found in the IFS at compile time. **You do not need to list directories that are part of the repository.** | ## Binding handled automatically If you are making use of service programs, Source Orbit will automatically maintain this for you. Your service programs will be added to the binding directory automatically and programs automatically include them. **No need to use the `BNDDIR` header** in your source code. Be careful, because changing the custom compile commands from the default may break this functionality. **You should still create binder source for all your service programs** in order for Source Orbit to create them automatically. +If your project contains a `.bnddir` file, then the basename of that file will be used as the binding directory name. If you do not have a `.bnddir` file, then the binding directory will be named `APP` automatically. + ## Changing compile options When you use `so -bf make`, Source Orbit will generate a makefile that contains the compile commands for each object. The compile commands can come from two places From 213dd2153f4173d960e848f9b140e57bef02ffc3 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Mon, 2 Jun 2025 10:13:37 -0400 Subject: [PATCH 10/10] Add support for using *LIBL with DCLF in CL Signed-off-by: worksofliam --- cli/src/targets.ts | 4 ++ cli/test/cldclf.test.ts | 45 +++++++++++++++++++++++ cli/test/fixtures/cldclf/apgm.pgm.clle | 3 ++ cli/test/fixtures/cldclf/department.table | 29 +++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 cli/test/cldclf.test.ts create mode 100644 cli/test/fixtures/cldclf/apgm.pgm.clle create mode 100644 cli/test/fixtures/cldclf/department.table diff --git a/cli/src/targets.ts b/cli/src/targets.ts index 0806a36..a384cb2 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -688,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.`, diff --git a/cli/test/cldclf.test.ts b/cli/test/cldclf.test.ts new file mode 100644 index 0000000..d6d7ff4 --- /dev/null +++ b/cli/test/cldclf.test.ts @@ -0,0 +1,45 @@ +import { beforeAll, describe, expect, test } from 'vitest'; + +import { Targets } from '../src/targets' +import { setupFixture } from './fixtures/projects'; +import { ReadFileSystem } from '../src/readFileSystem'; + +describe(`CL with DCLF`, () => { + const project = setupFixture(`cldclf`); + + const fs = new ReadFileSystem(); + const targets = new Targets(project.cwd, fs); + + beforeAll(async () => { + project.setup(); + await targets.loadProject(); + + expect(targets.getTargets().length).toBeGreaterThan(0); + targets.resolveBinder(); + }); + + test(`Objects are loaded`, () => { + expect(targets).toBeDefined(); + expect(targets.binderRequired()).toBeFalsy(); + + const targetObjects = targets.getTargets(); + + expect(targetObjects.length).toBe(2); + + expect(targetObjects.some(t => t.systemName === `APGM` && t.type === `PGM` && t.extension === `clle`)).toBeTruthy(); + expect(targetObjects.some(t => t.systemName === `DEPARTMENT` && t.type === `FILE` && t.extension === `table`)).toBeTruthy(); + }); + + test(`CL has valid dependency`, () => { + const apgm = targets.getTarget({systemName: `APGM`, type: `PGM`}); + expect(apgm).toBeDefined(); + + const logs = targets.logger.getLogsFor(apgm.relativePath); + console.log(logs); + expect(logs.length).toBe(0); + + expect(apgm.deps.length).toBe(1); + expect(apgm.deps[0].systemName).toBe(`DEPARTMENT`); + expect(apgm.deps[0].type).toBe(`FILE`); + }); +}); \ No newline at end of file diff --git a/cli/test/fixtures/cldclf/apgm.pgm.clle b/cli/test/fixtures/cldclf/apgm.pgm.clle new file mode 100644 index 0000000..36fd445 --- /dev/null +++ b/cli/test/fixtures/cldclf/apgm.pgm.clle @@ -0,0 +1,3 @@ +PGM + DCLF FILE(*LIBL/DEPARTMENT) OPNID(SAVRSTLIB) +ENDPGM \ No newline at end of file diff --git a/cli/test/fixtures/cldclf/department.table b/cli/test/fixtures/cldclf/department.table new file mode 100644 index 0000000..d6c3b0f --- /dev/null +++ b/cli/test/fixtures/cldclf/department.table @@ -0,0 +1,29 @@ +--https://www.ibm.com/docs/en/i/7.3?topic=tables-department-table-department + +CREATE OR REPLACE TABLE DEPARTMENT + (DEPTNO CHAR(3) NOT NULL, + DEPTNAME VARCHAR(36) NOT NULL, + MGRNO CHAR(6) NOT NULL, + ADMRDEPT CHAR(3) NOT NULL, + LOCATION CHAR(16) NOT NULL, + PRIMARY KEY (DEPTNO)); + +ALTER TABLE DEPARTMENT + ADD FOREIGN KEY ROD (ADMRDEPT) + REFERENCES DEPARTMENT + ON DELETE CASCADE; + +-- Remove circular reference +--ALTER TABLE DEPARTMENT +-- ADD FOREIGN KEY RDE (MGRNO) +-- REFERENCES EMPLOYEE +-- ON DELETE SET NULL; + +-- CREATE UNIQUE INDEX XDEPT1 +-- ON DEPARTMENT (DEPTNO); + +-- CREATE INDEX XDEPT2 +-- ON DEPARTMENT (MGRNO); + +-- CREATE INDEX XDEPT3 +-- ON DEPARTMENT (ADMRDEPT); \ No newline at end of file