From e5d70e865b9ca080ae51f0456a5dca207c1e6fb1 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 3 Nov 2023 08:43:00 -0400 Subject: [PATCH 01/10] Initial work to always require binder source --- cli/.vscode/launch.json | 1 + cli/src/targets.ts | 34 ---------------------------------- cli/test/createTargets.ts | 17 +++++++++++++++-- cli/test/project.test.ts | 19 ++++++++++--------- docs/pages/general/rules.md | 7 ++----- 5 files changed, 28 insertions(+), 50 deletions(-) diff --git a/cli/.vscode/launch.json b/cli/.vscode/launch.json index b91d74e..dea7673 100644 --- a/cli/.vscode/launch.json +++ b/cli/.vscode/launch.json @@ -11,6 +11,7 @@ "cwd": "${workspaceFolder:cli}", "program": "${workspaceFolder:cli}/dist/index.js", "sourceMaps": true, + "args": ["-d", "${workspaceFolder:ibmi-company_system-rmake}", "-bf", "json"], "preLaunchTask": { "type": "npm", "script": "webpack:dev" diff --git a/cli/src/targets.ts b/cli/src/targets.ts index 1ae658b..44b43be 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -1215,40 +1215,6 @@ export class Targets { } } - // Next, we loop through all the modules we know of and if that module - // is not a dependency on any service program, then we assume it's a - // service program object with EXPORT(*ALL) - const allServicePrograms = this.getParentObjects("SRVPGM"); - for (const module of allModules) { - const isBoundSomewhere = allServicePrograms.some(srvpgm => srvpgm.deps.some(dep => dep.name === module.name && dep.type === `MODULE`)); - if (!isBoundSomewhere) { - infoOut(`Assuming ${module.name}.${module.type} is a service program (SRVPGM)`); - - const newServiceProgramTarget: ILEObject = { - ...module, - type: `SRVPGM`, - relativePath: undefined, - extension: undefined, - exports: module.exports - }; - - // This creates the service program target if it does not exist. - const serviceProgramTarget = this.createOrAppend(newServiceProgramTarget, module); - - // Add this new service program to the project binding directory - this.createOrAppend(bindingDirectoryTarget, serviceProgramTarget); - - // Resolve the exports to this new service program - if (serviceProgramTarget.exports) { - serviceProgramTarget.exports.forEach(e => { - this.resolvedExports[e.toUpperCase()] = serviceProgramTarget; - }); - } - - infoOut(``); - } - } - // We loop through all programs and service programs and study their imports. // We do this in case they depend on another service programs based on import for (let target of deps) { diff --git a/cli/test/createTargets.ts b/cli/test/createTargets.ts index 86debfa..773ba50 100644 --- a/cli/test/createTargets.ts +++ b/cli/test/createTargets.ts @@ -7,24 +7,28 @@ export const cwd = path.join(`/`, `projects`); export function createTargets(withDeps = false) { const targets = new Targets(cwd); + // Command object for PROGRAMA.PGM const programACommand = targets.resolveObject(path.join(cwd, `qcmdsrc`, `programA.cmd`)); expect(programACommand.name).toBe(`PROGRAMA`); expect(programACommand.type).toBe(`CMD`); expect(programACommand.extension).toBe(`cmd`); expect(programACommand.relativePath).toBe(path.join(`qcmdsrc`, `programA.cmd`)); + // Command object that goes unused. const unusedCmd = targets.resolveObject(path.join(cwd, `qcmdsrc`, `unused.cmd`)); expect(unusedCmd.name).toBe(`UNUSED`); expect(unusedCmd.type).toBe(`CMD`); expect(unusedCmd.extension).toBe(`cmd`); expect(unusedCmd.relativePath).toBe(path.join(`qcmdsrc`, `unused.cmd`)); + // Program object const programA = targets.resolveObject(path.join(cwd, `qrpglesrc`, `programA.pgm.rpgle`)); expect(programA.name).toBe(`PROGRAMA`); expect(programA.type).toBe(`PGM`); expect(programA.extension).toBe(`rpgle`); expect(programA.relativePath).toBe(path.join(`qrpglesrc`, `programA.pgm.rpgle`)); + // Program object, imports TOLOWER const programB = targets.resolveObject(path.join(cwd, `qrpglesrc`, `programB.pgm.sqlrpgle`)); expect(programB.name).toBe(`PROGRAMB`); expect(programB.type).toBe(`PGM`); @@ -32,6 +36,7 @@ export function createTargets(withDeps = false) { expect(programB.relativePath).toBe(path.join(`qrpglesrc`, `programB.pgm.sqlrpgle`)); programB.imports = [`TOLOWER`]; + // Program object, imports TOLOWER const programC = targets.resolveObject(path.join(cwd, `qrpglesrc`, `programC.pgm.sqlrpgle`)); expect(programC.name).toBe(`PROGRAMC`); expect(programC.type).toBe(`PGM`); @@ -39,7 +44,7 @@ export function createTargets(withDeps = false) { expect(programC.relativePath).toBe(path.join(`qrpglesrc`, `programC.pgm.sqlrpgle`)); programC.imports = [`TOUPPER`]; - // moduleA is not depended on + // Module MODULEA.MODULE, which has no parents const moduleA = targets.resolveObject(path.join(cwd, `qrpglesrc`, `moduleA.rpgle`)); expect(moduleA.name).toBe(`MODULEA`); expect(moduleA.type).toBe(`MODULE`); @@ -47,6 +52,7 @@ export function createTargets(withDeps = false) { expect(moduleA.relativePath).toBe(path.join(`qrpglesrc`, `moduleA.rpgle`)); moduleA.exports = [`SUMNUMS`]; + // Module MODULEB.MODULE, which exports TOLOWER const moduleB = targets.resolveObject(path.join(cwd, `qrpglesrc`, `moduleB.sqlrpgle`)); expect(moduleB.name).toBe(`MODULEB`); expect(moduleB.type).toBe(`MODULE`); @@ -54,33 +60,39 @@ export function createTargets(withDeps = false) { expect(moduleB.relativePath).toBe(path.join(`qrpglesrc`, `moduleB.sqlrpgle`)); moduleB.exports = [`TOLOWER`]; + // SRVPGMA.SRVPGM, which imports TOLOWER from MODULEB.MODULE and therefore exports TOLOWER const srvpgmAModule = targets.resolveObject(path.join(cwd, `qrpglesrc`, `srvpgmA.rpgle`)); expect(srvpgmAModule.name).toBe(`SRVPGMA`); expect(srvpgmAModule.type).toBe(`MODULE`); expect(srvpgmAModule.extension).toBe(`rpgle`); expect(srvpgmAModule.relativePath).toBe(path.join(`qrpglesrc`, `srvpgmA.rpgle`)); srvpgmAModule.imports = [`TOLOWER`]; - srvpgmAModule.exports = [`TOUPPER`]; + srvpgmAModule.exports = [`TOLOWER`]; + // FILEA.FILE const fileA = targets.resolveObject(path.join(cwd, `qddssrc`, `fileA.sql`)); expect(fileA.name).toBe(`FILEA`); expect(fileA.type).toBe(`FILE`); expect(fileA.extension).toBe(`sql`); expect(fileA.relativePath).toBe(path.join(`qddssrc`, `fileA.sql`)); + // FILEB.FILE const fileB = targets.resolveObject(path.join(cwd, `qddssrc`, `fileB.pf`)); expect(fileB.name).toBe(`FILEB`); expect(fileB.type).toBe(`FILE`); expect(fileB.extension).toBe(`pf`); expect(fileB.relativePath).toBe(path.join(`qddssrc`, `fileB.pf`)); + // ORDENTSRV.SRVPGM, which exports/imports FIXTOTALS const ORDENTSRV = targets.resolveObject(path.join(cwd, `qbndsrc`, `ordentsrv.binder`)); ORDENTSRV.exports = [`FIXTOTALS`]; + ORDENTSRV.imports = [`FIXTOTALS`]; expect(ORDENTSRV.name).toBe(`ORDENTSRV`); expect(ORDENTSRV.type).toBe(`SRVPGM`); expect(ORDENTSRV.extension).toBe(`binder`); expect(ORDENTSRV.relativePath).toBe(path.join(`qbndsrc`, `ordentsrv.binder`)); + // ORDENTMOD.MODULE which exports FIXTOTALS const ORDENTMOD = targets.resolveObject(path.join(cwd, `qrpglesrc`, `ordentmod.rpgle`)); ORDENTMOD.exports = [`FIXTOTALS`]; expect(ORDENTMOD.name).toBe(`ORDENTMOD`); @@ -88,6 +100,7 @@ export function createTargets(withDeps = false) { expect(ORDENTMOD.extension).toBe(`rpgle`); expect(ORDENTMOD.relativePath).toBe(path.join(`qrpglesrc`, `ordentmod.rpgle`)); + // UNUSEDSRV.SRVPGM, which exports BIGNOPE const UNUSEDSRV = targets.resolveObject(path.join(cwd, `qbndsrc`, `unusedsrv.binder`)); UNUSEDSRV.exports = [`BIGNOPE`]; expect(UNUSEDSRV.name).toBe(`UNUSEDSRV`); diff --git a/cli/test/project.test.ts b/cli/test/project.test.ts index 4a2ee1a..c42430f 100644 --- a/cli/test/project.test.ts +++ b/cli/test/project.test.ts @@ -25,7 +25,7 @@ describe.skipIf(files.length === 0)(`ibmi-company_system tests`, () => { }); test(`Check objects are generated`, async () => { - expect(targets.getResolvedObjects().length).toBe(10); + expect(targets.getResolvedObjects().length).toBe(11); expect(targets.getDeps().length).toBe(12); expect(targets.getParentObjects(`FILE`).length).toBe(4); expect(targets.getParentObjects(`PGM`).length).toBe(3); @@ -102,16 +102,17 @@ describe.skipIf(files.length === 0)(`ibmi-company_system tests`, () => { expect(myBinder.deps.length).toBe(2); - const utilsSrvpgm = myBinder.deps[0]; - expect(utilsSrvpgm.name).toBe(`UTILS`); - expect(utilsSrvpgm.type).toBe(`SRVPGM`); - expect(utilsSrvpgm.relativePath).toBe(`qsrvsrc/utils.bnd`); + console.log(myBinder.deps); - const bankingSrvpgm = myBinder.deps[1]; + const bankingSrvpgm = myBinder.deps[0]; expect(bankingSrvpgm.name).toBe(`BANKING`); expect(bankingSrvpgm.type).toBe(`SRVPGM`); - // Because no binder source - expect(bankingSrvpgm.relativePath).toBeUndefined(); + expect(bankingSrvpgm.relativePath).toBe(`qsrvsrc/banking.bnd`); + + const utilsSrvpgm = myBinder.deps[1]; + expect(utilsSrvpgm.name).toBe(`UTILS`); + expect(utilsSrvpgm.type).toBe(`SRVPGM`); + expect(utilsSrvpgm.relativePath).toBe(`qsrvsrc/utils.bnd`); }); test(`Check employee table`, async () => { @@ -233,7 +234,7 @@ describe.skipIf(files.length === 0)(`ibmi-company_system tests`, () => { const lines = MakeProject.generateSpecificTarget(makeDefaults.compiles[`srvpgm`], myPgm); expect(lines.join()).toBe([ - '$(PREPATH)/BANKING.SRVPGM: ', + '$(PREPATH)/BANKING.SRVPGM: qsrvsrc/banking.bnd', '\t-system -q "CRTBNDDIR BNDDIR($(BIN_LIB)/$(APP_BNDDIR))"', '\t-system -q "RMVBNDDIRE BNDDIR($(BIN_LIB)/$(APP_BNDDIR)) OBJ(($(BIN_LIB)/BANKING))"', '\t-system "DLTOBJ OBJ($(BIN_LIB)/BANKING) OBJTYPE(*SRVPGM)"', diff --git a/docs/pages/general/rules.md b/docs/pages/general/rules.md index 8fdf4d2..8f99198 100644 --- a/docs/pages/general/rules.md +++ b/docs/pages/general/rules.md @@ -44,11 +44,8 @@ Source Orbit does not care about project structure, but does enforce these rules * if your source includes `.pgm`, then it will become a program. * `mypgm.pgm.rpgle` becomes a `MYPGM.PGM` object - * Source Orbit only supports single module programs * if your source does not include `.pgm`, then it will become a module, cmd, dtaara, etc. - * `mysrvpgm.rpgle` becomes `MYSRVPGM.MODULE` and then `MYSRVPGM.PGM` - * though this is only if this module is not automatically resolved via binder source. -* assumes binder sources (`.bnd`/`.binder`) is a service program. Source Orbit will scan the binder source to find the modules for the service program automatically. +* assumes binder source (`.bnd`/`.binder`) is a service program. Source Orbit will scan the binder source to find the exported functions/procedures from modules inside the project. * Source Orbit does not yet support SQL long name references. (Coming soon) ### SQL sources @@ -63,7 +60,7 @@ CREATE OR REPLACE TABLE CUSORD (...) ### Embedded SQL in RPGLE C specs -Source Orbit does not support embedded SQL (`exec sql`) used in a C spec. - no problem with mixed-format or free-format. We recommend +Source Orbit does not support embedded SQL (`exec sql`) used in a C spec. - no problem with mixed-format or free-format. We recommend: 1. converting the embedded SQL statements (`exec sql`) to not have C specs, or 2. you use user-open (`USROPN`) file definitions for those files, which you would never open. From 48d377aa93551006e99696ae7b15fd9a01edb121 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 3 Nov 2023 08:45:19 -0400 Subject: [PATCH 02/10] Updated submodule --- ibmi-company_system-rmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ibmi-company_system-rmake b/ibmi-company_system-rmake index 7577e31..4f81109 160000 --- a/ibmi-company_system-rmake +++ b/ibmi-company_system-rmake @@ -1 +1 @@ -Subproject commit 7577e31f502dfbe9f11687aee0998b8c3066bd38 +Subproject commit 4f81109bea69f18eed120e7bad969a0b2484ceb4 From 73c2d296da97864ce9e6127054679a128854d3f6 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 3 Nov 2023 09:17:29 -0400 Subject: [PATCH 03/10] Fix broken tests --- cli/test/{createTargets.ts => fixture.ts} | 50 ++++++++++++++++++++--- cli/test/make.test.ts | 21 ++++------ cli/test/project.test.ts | 2 - cli/test/targets.test.ts | 29 +++++-------- 4 files changed, 65 insertions(+), 37 deletions(-) rename cli/test/{createTargets.ts => fixture.ts} (71%) diff --git a/cli/test/createTargets.ts b/cli/test/fixture.ts similarity index 71% rename from cli/test/createTargets.ts rename to cli/test/fixture.ts index 773ba50..1cde551 100644 --- a/cli/test/createTargets.ts +++ b/cli/test/fixture.ts @@ -4,7 +4,7 @@ import path from 'path'; export const cwd = path.join(`/`, `projects`); -export function createTargets(withDeps = false) { +export function baseTargets(withDeps = false) { const targets = new Targets(cwd); // Command object for PROGRAMA.PGM @@ -61,11 +61,11 @@ export function createTargets(withDeps = false) { moduleB.exports = [`TOLOWER`]; // SRVPGMA.SRVPGM, which imports TOLOWER from MODULEB.MODULE and therefore exports TOLOWER - const srvpgmAModule = targets.resolveObject(path.join(cwd, `qrpglesrc`, `srvpgmA.rpgle`)); + const srvpgmAModule = targets.resolveObject(path.join(cwd, `qsrvsrc`, `srvpgmA.bnd`)); expect(srvpgmAModule.name).toBe(`SRVPGMA`); - expect(srvpgmAModule.type).toBe(`MODULE`); - expect(srvpgmAModule.extension).toBe(`rpgle`); - expect(srvpgmAModule.relativePath).toBe(path.join(`qrpglesrc`, `srvpgmA.rpgle`)); + expect(srvpgmAModule.type).toBe(`SRVPGM`); + expect(srvpgmAModule.extension).toBe(`bnd`); + expect(srvpgmAModule.relativePath).toBe(path.join(`qsrvsrc`, `srvpgmA.bnd`)); srvpgmAModule.imports = [`TOLOWER`]; srvpgmAModule.exports = [`TOLOWER`]; @@ -124,4 +124,44 @@ export function createTargets(withDeps = false) { } return targets; +} + +export function multiModuleObjects() { + const targets = new Targets(cwd); + + // Base program object MYWEBAPP.PGM + const myWebApp = targets.resolveObject(path.join(cwd, `qrpglesrc`, `mywebapp.pgm.rpgle`)); + myWebApp.imports = [`ROUTEHANDLERA`, `ROUTEHANDLERB`, `JWT_MIDDLEWARE`, `IL_LISTEN`, `IL_RESPONSEWRITESTREAM`]; + + // Module that is required by the MYWEBAPP.PGM + const handlerAMod = targets.resolveObject(path.join(cwd, `qrpglesrc`, `handlerA.rpgle`)); + handlerAMod.exports = [`ROUTEHANDLERA`]; + handlerAMod.imports = [`JSON_SQLRESULTSET`, `IL_RESPONSEWRITESTREAM`]; + + // Another module that is required by the MYWEBAPP.PGM + const handlerBMod = targets.resolveObject(path.join(cwd, `qrpglesrc`, `handlerA.rpgle`)); + handlerBMod.exports = [`ROUTEHANDLERB`]; + handlerBMod.imports = [`API_VALIDATE`, `JSON_SQLRESULTSET`, `IL_RESPONSEWRITESTREAM`]; + + // Another module that is part of the JWTHANDLER.SRVPGM object + const jwtHandlerMod = targets.resolveObject(path.join(cwd, `qrpglesrc`, `jwtHandler.rpgle`)); + jwtHandlerMod.exports = [`JWT_MIDDLEWARE`]; + + // Another module that is part of the JWTHANDLER.SRVPGM object + const validateMod = targets.resolveObject(path.join(cwd, `qrpglesrc`, `validate.rpgle`)); + validateMod.exports = [`API_VALIDATE`]; + + // Service program for JWTHANDLER, used by MYWEBAPP + const jwtHandlerSrv = targets.resolveObject(path.join(cwd, `qsrvsrc`, `utils.binder`)); + jwtHandlerSrv.imports = [`JWT_MIDDLEWARE`, `API_VALIDATE`]; + jwtHandlerSrv.exports = [`JWT_MIDDLEWARE`, `API_VALIDATE`]; + + targets.createOrAppend(myWebApp); + targets.createOrAppend(handlerAMod); + targets.createOrAppend(handlerBMod); + targets.createOrAppend(jwtHandlerMod); + targets.createOrAppend(validateMod); + targets.createOrAppend(jwtHandlerSrv); + + targets.resolveBinder(); } \ No newline at end of file diff --git a/cli/test/make.test.ts b/cli/test/make.test.ts index 7378656..eb07240 100644 --- a/cli/test/make.test.ts +++ b/cli/test/make.test.ts @@ -1,11 +1,11 @@ import { assert, expect, test } from 'vitest' import { Targets } from '../src/targets' import path from 'path'; -import { createTargets, cwd } from './createTargets'; +import { baseTargets, cwd } from './fixture'; import { MakeProject } from '../src/builders/make'; test('generateTargets (pre-resolve)', () => { - const targets = createTargets(true); + const targets = baseTargets(true); const project = new MakeProject(cwd, targets); const targetContent = project.generateTargets(); @@ -23,7 +23,7 @@ test('generateTargets (pre-resolve)', () => { }); test('generateTargets (post-resolve)', () => { - const targets = createTargets(true); + const targets = baseTargets(true); targets.resolveBinder(); @@ -36,22 +36,19 @@ test('generateTargets (post-resolve)', () => { 'all: $(PREPATH)/$(APP_BNDDIR).BNDDIR $(PREPATH)/PROGRAMA.PGM $(PREPATH)/PROGRAMB.PGM $(PREPATH)/PROGRAMA.CMD', '', '$(PREPATH)/PROGRAMA.PGM: $(PREPATH)/FILEA.FILE $(PREPATH)/PROGRAMB.PGM', - '$(PREPATH)/PROGRAMB.PGM: $(PREPATH)/MODULEB.SRVPGM', + '$(PREPATH)/PROGRAMB.PGM: $(PREPATH)/SRVPGMA.SRVPGM', '$(PREPATH)/MODULEA.MODULE: $(PREPATH)/FILEA.FILE $(PREPATH)/FILEB.FILE', '$(PREPATH)/MODULEB.MODULE: $(PREPATH)/FILEB.FILE', - `$(PREPATH)/SRVPGMA.MODULE: $(PREPATH)/MODULEB.SRVPGM`, + `$(PREPATH)/SRVPGMA.SRVPGM: $(PREPATH)/MODULEB.MODULE`, `$(PREPATH)/ORDENTSRV.SRVPGM: $(PREPATH)/ORDENTMOD.MODULE`, - `$(PREPATH)/PROGRAMA.CMD: $(PREPATH)/PROGRAMA.PGM`, - '$(PREPATH)/$(APP_BNDDIR).BNDDIR: $(PREPATH)/ORDENTSRV.SRVPGM $(PREPATH)/MODULEA.SRVPGM $(PREPATH)/MODULEB.SRVPGM $(PREPATH)/SRVPGMA.SRVPGM', - '$(PREPATH)/MODULEA.SRVPGM: $(PREPATH)/MODULEA.MODULE', - '$(PREPATH)/MODULEB.SRVPGM: $(PREPATH)/MODULEB.MODULE', - `$(PREPATH)/SRVPGMA.SRVPGM: $(PREPATH)/SRVPGMA.MODULE`, + '$(PREPATH)/PROGRAMA.CMD: $(PREPATH)/PROGRAMA.PGM', + '$(PREPATH)/$(APP_BNDDIR).BNDDIR: $(PREPATH)/SRVPGMA.SRVPGM $(PREPATH)/ORDENTSRV.SRVPGM', ] ); }); test('generateHeader (binder changes)', () => { - const targets = createTargets(true); + const targets = baseTargets(true); const project = new MakeProject(cwd, targets); @@ -69,7 +66,7 @@ test('generateHeader (binder changes)', () => { }); test('applySettings (binder)', () => { - const targets = createTargets(true); + const targets = baseTargets(true); const project = new MakeProject(cwd, targets); diff --git a/cli/test/project.test.ts b/cli/test/project.test.ts index c42430f..919a7fc 100644 --- a/cli/test/project.test.ts +++ b/cli/test/project.test.ts @@ -102,8 +102,6 @@ describe.skipIf(files.length === 0)(`ibmi-company_system tests`, () => { expect(myBinder.deps.length).toBe(2); - console.log(myBinder.deps); - const bankingSrvpgm = myBinder.deps[0]; expect(bankingSrvpgm.name).toBe(`BANKING`); expect(bankingSrvpgm.type).toBe(`SRVPGM`); diff --git a/cli/test/targets.test.ts b/cli/test/targets.test.ts index 4b06a37..ff9cfe3 100644 --- a/cli/test/targets.test.ts +++ b/cli/test/targets.test.ts @@ -1,12 +1,12 @@ import { expect, test } from 'vitest' -import { createTargets } from './createTargets'; +import { baseTargets } from './fixture'; test('resolveObject', () => { - createTargets(); + baseTargets(); }); test('createOrApend', () => { - const targets = createTargets(true); + const targets = baseTargets(true); const deps = targets.getDeps(); @@ -33,7 +33,7 @@ test('createOrApend', () => { }); test('resolveBinder', () => { - const targets = createTargets(true); + const targets = baseTargets(true); expect(targets.getDeps().length).toBe(10); expect(targets.binderRequired()).toBe(false); @@ -61,14 +61,14 @@ test('resolveBinder', () => { const deps = targets.getDeps(); - expect(deps.length).toBe(12); + expect(deps.length).toBe(9); expect(targets.binderRequired()).toBe(true); const bnddir = deps.find(d => d.name === `$(APP_BNDDIR)` && d.type === `BNDDIR`); expect(bnddir).toBeDefined(); expect(bnddir.extension).toBeUndefined(); expect(bnddir.relativePath).toBeUndefined(); - expect(bnddir.deps.length).toBe(4); + expect(bnddir.deps.length).toBe(2); for (const srvPgmDep of bnddir.deps) { // Ensure that the deps of the bnddir exist @@ -77,14 +77,8 @@ test('resolveBinder', () => { expect(srvPgm).toBeDefined(); expect(srvPgm.deps.length).toBe(1); - // ORDENTSRV actually has - if (srvPgm.name === `ORDENTSRV`) { - expect(srvPgm.relativePath).toBe(`qbndsrc/ordentsrv.binder`); - expect(srvPgm.extension).toBe(`binder`); - } else { - expect(srvPgm.relativePath).toBeUndefined(); - expect(srvPgm.extension).toBeUndefined(); - } + expect(srvPgm.relativePath).toBeDefined(); + expect(srvPgm.extension).toBeDefined(); } const programACmd = deps.find(d => d.name === `PROGRAMA` && d.type === `CMD`); @@ -94,15 +88,14 @@ test('resolveBinder', () => { }); test('getObjectsByExtension', () => { - const targets = createTargets(true); + const targets = baseTargets(true); const rpglePrograms = targets.getObjectsByExtension(`pgm.rpgle`); expect(rpglePrograms.length).toBe(1); expect(rpglePrograms[0].relativePath).toBe(`qrpglesrc/programA.pgm.rpgle`); const rpgleModules = targets.getObjectsByExtension(`rpgle`); - expect(rpgleModules.length).toBe(3); + expect(rpgleModules.length).toBe(2); expect(rpgleModules[0].relativePath).toBe(`qrpglesrc/moduleA.rpgle`); - expect(rpgleModules[1].relativePath).toBe(`qrpglesrc/srvpgmA.rpgle`); - expect(rpgleModules[2].relativePath).toBe(`qrpglesrc/ordentmod.rpgle`); + expect(rpgleModules[1].relativePath).toBe(`qrpglesrc/ordentmod.rpgle`); }) \ No newline at end of file From 6a4c844d062f946d5942fc14e1e2271fa3ddf249 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 3 Nov 2023 09:18:23 -0400 Subject: [PATCH 04/10] Fix comments --- cli/test/fixture.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/test/fixture.ts b/cli/test/fixture.ts index 1cde551..c15b278 100644 --- a/cli/test/fixture.ts +++ b/cli/test/fixture.ts @@ -44,7 +44,7 @@ export function baseTargets(withDeps = false) { expect(programC.relativePath).toBe(path.join(`qrpglesrc`, `programC.pgm.sqlrpgle`)); programC.imports = [`TOUPPER`]; - // Module MODULEA.MODULE, which has no parents + // Module MODULEA.MODULE, which is not used at all. const moduleA = targets.resolveObject(path.join(cwd, `qrpglesrc`, `moduleA.rpgle`)); expect(moduleA.name).toBe(`MODULEA`); expect(moduleA.type).toBe(`MODULE`); @@ -100,7 +100,7 @@ export function baseTargets(withDeps = false) { expect(ORDENTMOD.extension).toBe(`rpgle`); expect(ORDENTMOD.relativePath).toBe(path.join(`qrpglesrc`, `ordentmod.rpgle`)); - // UNUSEDSRV.SRVPGM, which exports BIGNOPE + // UNUSEDSRV.SRVPGM, which exports BIGNOPE and is not used. const UNUSEDSRV = targets.resolveObject(path.join(cwd, `qbndsrc`, `unusedsrv.binder`)); UNUSEDSRV.exports = [`BIGNOPE`]; expect(UNUSEDSRV.name).toBe(`UNUSEDSRV`); From c98432279638c98a7e0b3993531000c59e0d1e60 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 3 Nov 2023 10:51:23 -0400 Subject: [PATCH 05/10] Multi module program and service program support --- cli/src/builders/make.ts | 21 ++++++++++++--- cli/src/targets.ts | 56 ++++++++++++++++++++++++++++----------- cli/test/fixture.ts | 4 ++- cli/test/make.test.ts | 57 ++++++++++++++++++++++++++++++++++++++-- cli/test/project.test.ts | 4 +-- cli/test/targets.test.ts | 44 +++++++++++++++++++++++++------ 6 files changed, 153 insertions(+), 33 deletions(-) diff --git a/cli/src/builders/make.ts b/cli/src/builders/make.ts index ca8412e..025c02a 100644 --- a/cli/src/builders/make.ts +++ b/cli/src/builders/make.ts @@ -41,6 +41,10 @@ export class MakeProject { binders: [], includePaths: [], compiles: { + "pgm": { + becomes: `PGM`, + command: `CRTPGM PGM($(BIN_LIB)/$*) ENTRY($*) MODULES(*MODULES) TGTRLS(*CURRENT) TGTCCSID(*JOB) BNDDIR($(BNDDIR)) DFTACTGRP(*no)` // TODO: fix this + }, "pgm.rpgle": { becomes: `PGM`, command: `CRTBNDRPG PGM($(BIN_LIB)/$*) SRCSTMF('$<') OPTION(*EVENTF) DBGVIEW(*SOURCE) TGTRLS(*CURRENT) TGTCCSID(*JOB) BNDDIR($(BNDDIR)) DFTACTGRP(*no)` @@ -52,6 +56,10 @@ export class MakeProject { ], command: `CRTSQLRPGI OBJ($(BIN_LIB)/$*) SRCSTMF('$<') COMMIT(*NONE) DBGVIEW(*SOURCE) OPTION(*EVENTF) COMPILEOPT('BNDDIR($(BNDDIR)) DFTACTGRP(*no)')` }, + "rpgle": { + becomes: `MODULE`, + command: `CRTRPGMOD MODULE($(BIN_LIB)/$*) SRCSTMF('$<') OPTION(*EVENTF) DBGVIEW(*SOURCE) TGTRLS(*CURRENT) TGTCCSID(*JOB)` + }, "sqlrpgle": { becomes: "MODULE", preCommands: [ @@ -89,14 +97,13 @@ export class MakeProject { binder: binderSourceCompile, bnd: binderSourceCompile, srvpgm: { - sourceOptional: true, becomes: `SRVPGM`, preCommands: [ `-system -q "CRTBNDDIR BNDDIR($(BIN_LIB)/$(APP_BNDDIR))"`, `-system -q "RMVBNDDIRE BNDDIR($(BIN_LIB)/$(APP_BNDDIR)) OBJ(($(BIN_LIB)/$*))"`, `-system "DLTOBJ OBJ($(BIN_LIB)/$*) OBJTYPE(*SRVPGM)"` ], - command: `CRTSRVPGM SRVPGM($(BIN_LIB)/$*) MODULE(*SRVPGM) EXPORT(*ALL) BNDDIR($(BNDDIR))`, + command: `CRTSRVPGM SRVPGM($(BIN_LIB)/$*) MODULE(*MODULES) SRCSTMF('$<') BNDDIR($(BNDDIR))`, postCommands: [ `-system -q "ADDBNDDIRE BNDDIR($(BIN_LIB)/$(APP_BNDDIR)) OBJ((*LIBL/$* *SRVPGM *IMMED))"` ] @@ -143,6 +150,10 @@ export class MakeProject { } } + public getSettings() { + return this.settings; + } + public applySettings(input: iProject) { if (input.includePaths && input.includePaths.length > 0) { this.settings.includePaths = input.includePaths; @@ -320,6 +331,8 @@ export class MakeProject { return command; } + const resolvedCommand = resolve(data.command); + lines.push( `$(PREPATH)/${ileObject.name}.${ileObject.type}: ${ileObject.relativePath || ``}`, ...(qsysTempName && data.member ? @@ -331,12 +344,12 @@ export class MakeProject { ...(data.command ? [ `\tliblist -c $(BIN_LIB);\\`, - `\tsystem "${resolve(data.command)}" > .logs/${ileObject.name.toLowerCase()}.splf` // TODO: write the spool file somewhere? + `\tsystem "${resolvedCommand}" > .logs/${ileObject.name.toLowerCase()}.splf` // TODO: write the spool file somewhere? ] : [] ), ...(data.postCommands ? data.postCommands.map(cmd => `\t${resolve(cmd)}`) : []), - `\tsystem "CPYTOSTMF FROMMBR('$(PREPATH)/EVFEVENT.FILE/${ileObject.name}.MBR') TOSTMF('.evfevent/${ileObject.name.toLowerCase()}.evfevent') DBFCCSID(*FILE) STMFCCSID(1208) STMFOPT(*REPLACE)"` + ...(resolvedCommand.includes(`*EVENTF`) ? [`\tsystem "CPYTOSTMF FROMMBR('$(PREPATH)/EVFEVENT.FILE/${ileObject.name}.MBR') TOSTMF('.evfevent/${ileObject.name.toLowerCase()}.evfevent') DBFCCSID(*FILE) STMFCCSID(1208) STMFOPT(*REPLACE)"`] : []), ); return lines; diff --git a/cli/src/targets.ts b/cli/src/targets.ts index 44b43be..636e409 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -1226,12 +1226,24 @@ export class Targets { target.imports.forEach(importName => { // Find if this import resolves to another object - const possibleDep = this.resolvedExports[importName.toUpperCase()]; + const possibleSrvPgmDep = this.resolvedExports[importName.toUpperCase()]; // We can't add a module as a dependency at this step. - if (possibleDep && possibleDep.type === `SRVPGM`) { + if (possibleSrvPgmDep && possibleSrvPgmDep.type === `SRVPGM`) { // Make sure we haven't imported it before! - if (!newImports.some(i => i.name === possibleDep.name && i.type === possibleDep.type)) { - newImports.push(possibleDep); + if (!newImports.some(i => i.name === possibleSrvPgmDep.name && i.type === possibleSrvPgmDep.type)) { + newImports.push(possibleSrvPgmDep); + } + + } else if (target.type === `PGM`) { + // Perhaps we're looking at a program object, which actually should be a multi + // module program, so we do a lookup for additional modules. + const possibleModuleDep = allModules.find(mod => mod.exports.includes(importName.toUpperCase())) + if (possibleModuleDep) { + if (!newImports.some(i => i.name === possibleModuleDep.name && i.type === possibleModuleDep.type)) { + newImports.push(possibleModuleDep); + + // TODO: consider other IMPORTS that `possibleModuleDep` needs. + } } } }); @@ -1239,6 +1251,14 @@ export class Targets { if (newImports.length > 0) { infoOut(`${target.name}.${target.type} has additional dependencies: ${newImports.map(i => `${i.name}.${i.type}`)}`); target.deps.push(...newImports); + + if (target.type === `PGM`) { + // If this program has MODULE dependecies, that means we need to change the way it's compiled + // to be a program made up of many modules, usually done with CRTPGM + if (target.deps.some(d => d.type === `MODULE`)) { + this.convertBoundProgramToMultiModuleProgram(target); + } + } } } } @@ -1266,6 +1286,22 @@ export class Targets { } } + private convertBoundProgramToMultiModuleProgram(currentTarget: ILEObjectTarget) { + const objectAsMod: ILEObject = { + ...currentTarget, + type: `MODULE` + }; + + this.resolvedObjects[path.join(this.cwd, currentTarget.relativePath)].extension = `pgm`; + this.pushDep({ + ...objectAsMod, + deps: [] + }); + + currentTarget.deps.push(objectAsMod); + currentTarget.extension = `pgm`; // Change the extension so it's picked up correctly during the build process. + } + public createOrAppend(parentObject: ILEObject, newDep?: ILEObject) { let existingTarget = this.deps[`${parentObject.name}.${parentObject.type}`]; @@ -1308,17 +1344,7 @@ export class Targets { public getObjectsByExtension(ext: string): ILEObject[] { const upperExt = ext.toUpperCase(); - return Object. - keys(this.resolvedObjects). - filter(filePath => { - const basename = path.basename(filePath); - const dotIndex = basename.indexOf(`.`); - if (dotIndex >= 0) { - const lastPart = basename.substring(dotIndex + 1); - return (lastPart.toUpperCase() === upperExt); - } - }). - map(filePath => this.resolvedObjects[filePath]); + return Object.values(this.resolvedObjects).filter(obj => obj.extension?.toUpperCase() == upperExt); } public getExports() { diff --git a/cli/test/fixture.ts b/cli/test/fixture.ts index c15b278..92fe454 100644 --- a/cli/test/fixture.ts +++ b/cli/test/fixture.ts @@ -139,7 +139,7 @@ export function multiModuleObjects() { handlerAMod.imports = [`JSON_SQLRESULTSET`, `IL_RESPONSEWRITESTREAM`]; // Another module that is required by the MYWEBAPP.PGM - const handlerBMod = targets.resolveObject(path.join(cwd, `qrpglesrc`, `handlerA.rpgle`)); + const handlerBMod = targets.resolveObject(path.join(cwd, `qrpglesrc`, `handlerB.rpgle`)); handlerBMod.exports = [`ROUTEHANDLERB`]; handlerBMod.imports = [`API_VALIDATE`, `JSON_SQLRESULTSET`, `IL_RESPONSEWRITESTREAM`]; @@ -164,4 +164,6 @@ export function multiModuleObjects() { targets.createOrAppend(jwtHandlerSrv); targets.resolveBinder(); + + return targets; } \ No newline at end of file diff --git a/cli/test/make.test.ts b/cli/test/make.test.ts index eb07240..7f32305 100644 --- a/cli/test/make.test.ts +++ b/cli/test/make.test.ts @@ -1,7 +1,7 @@ import { assert, expect, test } from 'vitest' import { Targets } from '../src/targets' import path from 'path'; -import { baseTargets, cwd } from './fixture'; +import { baseTargets, cwd, multiModuleObjects } from './fixture'; import { MakeProject } from '../src/builders/make'; test('generateTargets (pre-resolve)', () => { @@ -85,4 +85,57 @@ test('applySettings (binder)', () => { const headerContentB = project.generateHeader(); expect(headerContentB[bndDirIndex]).toBe(`BNDDIR=($(BIN_LIB)/$(APP_BNDDIR)) (TESTING)`); -}); \ No newline at end of file +}); + +test(`Multi-module program and service program`, () => { + const targets = multiModuleObjects(); + + const project = new MakeProject(cwd, targets); + const settings = project.getSettings(); + + project.applySettings({ + binders: [`ILEASTIC`, `NOXDB`] + }); + + const headerContent = project.generateTargets(); + + expect(headerContent.join()).toBe([ + 'all: $(PREPATH)/$(APP_BNDDIR).BNDDIR $(PREPATH)/MYWEBAPP.PGM', + '', + '$(PREPATH)/MYWEBAPP.PGM: $(PREPATH)/HANDLERA.MODULE $(PREPATH)/HANDLERB.MODULE $(PREPATH)/UTILS.SRVPGM $(PREPATH)/MYWEBAPP.MODULE', + '$(PREPATH)/HANDLERB.MODULE: $(PREPATH)/UTILS.SRVPGM', + '$(PREPATH)/UTILS.SRVPGM: $(PREPATH)/JWTHANDLER.MODULE $(PREPATH)/VALIDATE.MODULE', + '$(PREPATH)/$(APP_BNDDIR).BNDDIR: $(PREPATH)/UTILS.SRVPGM' + ].join()); + + const webappPgm = targets.getDep({name: `MYWEBAPP`, type: `PGM`}); + const webPgmTarget = MakeProject.generateSpecificTarget(settings.compiles[`pgm`], webappPgm); + expect(webPgmTarget.join()).toBe([ + '$(PREPATH)/MYWEBAPP.PGM: qrpglesrc/mywebapp.pgm.rpgle', + '\tliblist -c $(BIN_LIB);\\', + '\tsystem "CRTPGM PGM($(BIN_LIB)/MYWEBAPP) ENTRY(MYWEBAPP) MODULES(HANDLERA HANDLERB MYWEBAPP) TGTRLS(*CURRENT) TGTCCSID(*JOB) BNDDIR($(BNDDIR)) DFTACTGRP(*no)" > .logs/mywebapp.splf' + ].join()); + + const webappMod = targets.getDep({name: `MYWEBAPP`, type: `MODULE`}); + const webModTarget = MakeProject.generateSpecificTarget(settings.compiles[`rpgle`], webappMod); + expect(webModTarget.join()).toBe([ + '$(PREPATH)/MYWEBAPP.MODULE: qrpglesrc/mywebapp.pgm.rpgle', + '\tliblist -c $(BIN_LIB);\\', + `\tsystem "CRTRPGMOD MODULE($(BIN_LIB)/MYWEBAPP) SRCSTMF('qrpglesrc/mywebapp.pgm.rpgle') OPTION(*EVENTF) DBGVIEW(*SOURCE) TGTRLS(*CURRENT) TGTCCSID(*JOB)" > .logs/mywebapp.splf`, + `\tsystem "CPYTOSTMF FROMMBR('$(PREPATH)/EVFEVENT.FILE/MYWEBAPP.MBR') TOSTMF('.evfevent/mywebapp.evfevent') DBFCCSID(*FILE) STMFCCSID(1208) STMFOPT(*REPLACE)"` + ].join()); + + const utilsSrvpgm = targets.getDep({name: `UTILS`, type: `SRVPGM`}); + const utilsTarget = MakeProject.generateSpecificTarget(settings.compiles[`srvpgm`], utilsSrvpgm); + + console.log(utilsTarget); + expect(utilsTarget.join()).toBe([ + '$(PREPATH)/UTILS.SRVPGM: qsrvsrc/utils.binder', + '\t-system -q "CRTBNDDIR BNDDIR($(BIN_LIB)/$(APP_BNDDIR))"', + '\t-system -q "RMVBNDDIRE BNDDIR($(BIN_LIB)/$(APP_BNDDIR)) OBJ(($(BIN_LIB)/UTILS))"', + '\t-system "DLTOBJ OBJ($(BIN_LIB)/UTILS) OBJTYPE(*SRVPGM)"', + '\tliblist -c $(BIN_LIB);\\', + `\tsystem "CRTSRVPGM SRVPGM($(BIN_LIB)/UTILS) MODULE(JWTHANDLER VALIDATE) SRCSTMF('qsrvsrc/utils.binder') BNDDIR($(BNDDIR))" > .logs/utils.splf`, + '\t-system -q "ADDBNDDIRE BNDDIR($(BIN_LIB)/$(APP_BNDDIR)) OBJ((*LIBL/UTILS *SRVPGM *IMMED))"' + ].join()); +}) \ No newline at end of file diff --git a/cli/test/project.test.ts b/cli/test/project.test.ts index 919a7fc..f019c0f 100644 --- a/cli/test/project.test.ts +++ b/cli/test/project.test.ts @@ -223,7 +223,6 @@ describe.skipIf(files.length === 0)(`ibmi-company_system tests`, () => { '\tliblist -c $(BIN_LIB);\\', `\tsystem "CRTSRVPGM SRVPGM($(BIN_LIB)/UTILS) MODULE(UTILS) SRCSTMF('qsrvsrc/utils.bnd') BNDDIR($(BNDDIR))" > .logs/utils.splf`, '\t-system -q "ADDBNDDIRE BNDDIR($(BIN_LIB)/$(APP_BNDDIR)) OBJ((*LIBL/UTILS *SRVPGM *IMMED))"', - `\tsystem "CPYTOSTMF FROMMBR(\'$(PREPATH)/EVFEVENT.FILE/UTILS.MBR\') TOSTMF(\'.evfevent/utils.evfevent\') DBFCCSID(*FILE) STMFCCSID(1208) STMFOPT(*REPLACE)"` ].join()); }); @@ -237,9 +236,8 @@ describe.skipIf(files.length === 0)(`ibmi-company_system tests`, () => { '\t-system -q "RMVBNDDIRE BNDDIR($(BIN_LIB)/$(APP_BNDDIR)) OBJ(($(BIN_LIB)/BANKING))"', '\t-system "DLTOBJ OBJ($(BIN_LIB)/BANKING) OBJTYPE(*SRVPGM)"', '\tliblist -c $(BIN_LIB);\\', - '\tsystem "CRTSRVPGM SRVPGM($(BIN_LIB)/BANKING) MODULE(*SRVPGM) EXPORT(*ALL) BNDDIR($(BNDDIR))" > .logs/banking.splf', + '\tsystem "CRTSRVPGM SRVPGM($(BIN_LIB)/BANKING) MODULE(BANKING) SRCSTMF(\'qsrvsrc/banking.bnd\') BNDDIR($(BNDDIR))" > .logs/banking.splf', '\t-system -q "ADDBNDDIRE BNDDIR($(BIN_LIB)/$(APP_BNDDIR)) OBJ((*LIBL/BANKING *SRVPGM *IMMED))"', - `\tsystem "CPYTOSTMF FROMMBR(\'$(PREPATH)/EVFEVENT.FILE/BANKING.MBR\') TOSTMF(\'.evfevent/banking.evfevent\') DBFCCSID(*FILE) STMFCCSID(1208) STMFOPT(*REPLACE)"` ].join()); }); diff --git a/cli/test/targets.test.ts b/cli/test/targets.test.ts index ff9cfe3..e80cb1c 100644 --- a/cli/test/targets.test.ts +++ b/cli/test/targets.test.ts @@ -1,5 +1,5 @@ import { expect, test } from 'vitest' -import { baseTargets } from './fixture'; +import { baseTargets, multiModuleObjects } from './fixture'; test('resolveObject', () => { baseTargets(); @@ -90,12 +90,40 @@ test('resolveBinder', () => { test('getObjectsByExtension', () => { const targets = baseTargets(true); - const rpglePrograms = targets.getObjectsByExtension(`pgm.rpgle`); - expect(rpglePrograms.length).toBe(1); + const rpglePrograms = targets.getObjectsByExtension(`rpgle`); + expect(rpglePrograms.length).toBe(3); expect(rpglePrograms[0].relativePath).toBe(`qrpglesrc/programA.pgm.rpgle`); + expect(rpglePrograms[1].relativePath).toBe(`qrpglesrc/moduleA.rpgle`); + expect(rpglePrograms[2].relativePath).toBe(`qrpglesrc/ordentmod.rpgle`); +}) - const rpgleModules = targets.getObjectsByExtension(`rpgle`); - expect(rpgleModules.length).toBe(2); - expect(rpgleModules[0].relativePath).toBe(`qrpglesrc/moduleA.rpgle`); - expect(rpgleModules[1].relativePath).toBe(`qrpglesrc/ordentmod.rpgle`); -}) \ No newline at end of file +test(`Multi-module program and service programs`, () => { + const targets = multiModuleObjects(); + const deps = targets.getDeps(); + + const programs = targets.getResolvedObjects(`PGM`); + expect(programs.length).toBe(1); + + const srvPgms = targets.getResolvedObjects(`SRVPGM`); + expect(srvPgms.length).toBe(1); + + const modules = targets.getResolvedObjects(`MODULE`); + expect(modules.length).toBe(4); + + const webappPgm = programs[0]; + expect(webappPgm.extension).toBe(`pgm`); + const webappDef = deps.find(d => d.name === webappPgm.name && d.type === webappPgm.type); + expect(webappDef).toBeDefined(); + expect(webappDef.extension).toBe(`pgm`); + + expect(webappDef.deps.length).toBe(4); + expect(webappDef.deps.filter(d => d.type === `MODULE`).length).toBe(3); + expect(webappDef.deps.filter(d => d.type === `SRVPGM`).length).toBe(1); + + const utilsSrvPgm = srvPgms[0]; + const utilsDef = deps.find(d => d.name === utilsSrvPgm.name && d.type === utilsSrvPgm.type); + expect(utilsDef).toBeDefined(); + + expect(utilsDef.deps.length).toBe(2); + expect(utilsDef.deps.filter(d => d.type === `MODULE`).length).toBe(2); +}); \ No newline at end of file From 512de16ec6ee56aeef85f34503c25123a736518c Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 3 Nov 2023 11:00:22 -0400 Subject: [PATCH 06/10] Fix broken search by extension --- cli/src/targets.ts | 11 +++++++++-- cli/test/targets.test.ts | 11 +++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/cli/src/targets.ts b/cli/src/targets.ts index 636e409..038acb2 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -1343,8 +1343,15 @@ export class Targets { } public getObjectsByExtension(ext: string): ILEObject[] { - const upperExt = ext.toUpperCase(); - return Object.values(this.resolvedObjects).filter(obj => obj.extension?.toUpperCase() == upperExt); + const extensionParts = ext.split(`.`); + let extension = ext.toUpperCase(), shouldBeProgram = false; + + if (extensionParts.length === 2 && extensionParts[0].toUpperCase() === `PGM`) { + extension = extensionParts[1].toUpperCase(); + shouldBeProgram = true; + } + + return Object.values(this.resolvedObjects).filter(obj => obj.extension?.toUpperCase() === extension && (obj.type === `PGM`) === shouldBeProgram); } public getExports() { diff --git a/cli/test/targets.test.ts b/cli/test/targets.test.ts index e80cb1c..5db14bf 100644 --- a/cli/test/targets.test.ts +++ b/cli/test/targets.test.ts @@ -90,11 +90,14 @@ test('resolveBinder', () => { test('getObjectsByExtension', () => { const targets = baseTargets(true); - const rpglePrograms = targets.getObjectsByExtension(`rpgle`); - expect(rpglePrograms.length).toBe(3); + const rpglePrograms = targets.getObjectsByExtension(`pgm.rpgle`); + expect(rpglePrograms.length).toBe(1); expect(rpglePrograms[0].relativePath).toBe(`qrpglesrc/programA.pgm.rpgle`); - expect(rpglePrograms[1].relativePath).toBe(`qrpglesrc/moduleA.rpgle`); - expect(rpglePrograms[2].relativePath).toBe(`qrpglesrc/ordentmod.rpgle`); + + const rpgleModules = targets.getObjectsByExtension(`rpgle`); + expect(rpgleModules.length).toBe(2); + expect(rpgleModules[0].relativePath).toBe(`qrpglesrc/moduleA.rpgle`); + expect(rpgleModules[1].relativePath).toBe(`qrpglesrc/ordentmod.rpgle`); }) test(`Multi-module program and service programs`, () => { From 46349c630da7874ea233e9148c647e2621ef4686 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 3 Nov 2023 11:11:44 -0400 Subject: [PATCH 07/10] Further fixes for correctly getting objects --- cli/src/targets.ts | 15 +++++++++++++-- cli/test/make.test.ts | 1 - 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/cli/src/targets.ts b/cli/src/targets.ts index 038acb2..8956aac 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -1342,16 +1342,27 @@ export class Targets { return this.resolvedObjects[fullPath]; } + /** + * This API is a little trick. + * You can pass in a valid file extension, or if you pass + * solely just `pgm`, it will return all programs that + * have multiple modules. + */ public getObjectsByExtension(ext: string): ILEObject[] { const extensionParts = ext.split(`.`); - let extension = ext.toUpperCase(), shouldBeProgram = false; + let extension = ext.toUpperCase(), shouldBeProgram = false, anyPrograms = false; if (extensionParts.length === 2 && extensionParts[0].toUpperCase() === `PGM`) { extension = extensionParts[1].toUpperCase(); shouldBeProgram = true; + } else if (extension === `PGM`) { + anyPrograms = true; } - return Object.values(this.resolvedObjects).filter(obj => obj.extension?.toUpperCase() === extension && (obj.type === `PGM`) === shouldBeProgram); + return Object.values(this.resolvedObjects).filter(obj => + (obj.extension?.toUpperCase() === extension && (obj.type === `PGM`) === shouldBeProgram) || + (anyPrograms === true && obj.type === `PGM` && obj.extension.toUpperCase() === extension) + ); } public getExports() { diff --git a/cli/test/make.test.ts b/cli/test/make.test.ts index 7f32305..8e4dcb9 100644 --- a/cli/test/make.test.ts +++ b/cli/test/make.test.ts @@ -128,7 +128,6 @@ test(`Multi-module program and service program`, () => { const utilsSrvpgm = targets.getDep({name: `UTILS`, type: `SRVPGM`}); const utilsTarget = MakeProject.generateSpecificTarget(settings.compiles[`srvpgm`], utilsSrvpgm); - console.log(utilsTarget); expect(utilsTarget.join()).toBe([ '$(PREPATH)/UTILS.SRVPGM: qsrvsrc/utils.binder', '\t-system -q "CRTBNDDIR BNDDIR($(BIN_LIB)/$(APP_BNDDIR))"', From c5dff26402264c9e50016fce17c39f9c4921fc16 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 3 Nov 2023 11:32:40 -0400 Subject: [PATCH 08/10] Fix circular dep issue and import names --- cli/src/targets.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/cli/src/targets.ts b/cli/src/targets.ts index 8956aac..0a94492 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -1129,12 +1129,19 @@ export class Targets { .map(ref => { const keyword = ref.keyword; let importName: string = ref.name; - const extproc = keyword[`EXTPROC`]; + const extproc: string|boolean = keyword[`EXTPROC`]; if (extproc) { if (extproc === true) importName = ref.name; - else importName = trimQuotes(extproc); + else importName = extproc; } + if (importName.includes(`:`)) { + const parmParms = importName.split(`:`); + importName = parmParms.filter(p => !p.startsWith(`*`)).join(``); + } + + importName = trimQuotes(importName); + return importName; }); @@ -1288,7 +1295,12 @@ export class Targets { private convertBoundProgramToMultiModuleProgram(currentTarget: ILEObjectTarget) { const objectAsMod: ILEObject = { - ...currentTarget, + name: currentTarget.name, + extension: currentTarget.extension, + relativePath: currentTarget.relativePath, + imports: currentTarget.imports, + exports: currentTarget.exports, + text: currentTarget.text, type: `MODULE` }; @@ -1300,6 +1312,8 @@ export class Targets { currentTarget.deps.push(objectAsMod); currentTarget.extension = `pgm`; // Change the extension so it's picked up correctly during the build process. + currentTarget.imports = []; + currentTarget.exports = []; } public createOrAppend(parentObject: ILEObject, newDep?: ILEObject) { From 223c8254fda11bf5c7191dafff462e506360e8e2 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 3 Nov 2023 14:04:41 -0400 Subject: [PATCH 09/10] Fix issue with program not correctly converting to a module --- cli/src/targets.ts | 39 +++++++++++++++++++++------------------ cli/test/make.test.ts | 2 +- cli/test/targets.test.ts | 2 +- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/cli/src/targets.ts b/cli/src/targets.ts index 0a94492..ba4669c 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -1294,26 +1294,29 @@ export class Targets { } private convertBoundProgramToMultiModuleProgram(currentTarget: ILEObjectTarget) { - const objectAsMod: ILEObject = { - name: currentTarget.name, - extension: currentTarget.extension, - relativePath: currentTarget.relativePath, - imports: currentTarget.imports, - exports: currentTarget.exports, - text: currentTarget.text, - type: `MODULE` - }; + const basePath = currentTarget.relativePath; - this.resolvedObjects[path.join(this.cwd, currentTarget.relativePath)].extension = `pgm`; - this.pushDep({ - ...objectAsMod, - deps: [] - }); + // First, let's change this current target to be solely a program + // Change the extension so it's picked up correctly during the build process. + currentTarget.extension = `pgm`; + currentTarget.relativePath = undefined; + + // Store new resolved path for this object + this.storeResolved(path.join(this.cwd, `${currentTarget.name}.PGM`), currentTarget); + + // Then we can resolve the same path again + const newModule = this.resolveObject(path.join(this.cwd, basePath)); + // Force it as a module + newModule.type = `MODULE`; + + // Create a new target for the module + const newModTarget = this.createOrAppend(newModule); + + // Clean up imports for module and program + newModTarget.imports = currentTarget.imports; + currentTarget.imports = undefined; - currentTarget.deps.push(objectAsMod); - currentTarget.extension = `pgm`; // Change the extension so it's picked up correctly during the build process. - currentTarget.imports = []; - currentTarget.exports = []; + this.createOrAppend(currentTarget, newModule); } public createOrAppend(parentObject: ILEObject, newDep?: ILEObject) { diff --git a/cli/test/make.test.ts b/cli/test/make.test.ts index 8e4dcb9..d58f90a 100644 --- a/cli/test/make.test.ts +++ b/cli/test/make.test.ts @@ -111,7 +111,7 @@ test(`Multi-module program and service program`, () => { const webappPgm = targets.getDep({name: `MYWEBAPP`, type: `PGM`}); const webPgmTarget = MakeProject.generateSpecificTarget(settings.compiles[`pgm`], webappPgm); expect(webPgmTarget.join()).toBe([ - '$(PREPATH)/MYWEBAPP.PGM: qrpglesrc/mywebapp.pgm.rpgle', + '$(PREPATH)/MYWEBAPP.PGM: ', '\tliblist -c $(BIN_LIB);\\', '\tsystem "CRTPGM PGM($(BIN_LIB)/MYWEBAPP) ENTRY(MYWEBAPP) MODULES(HANDLERA HANDLERB MYWEBAPP) TGTRLS(*CURRENT) TGTCCSID(*JOB) BNDDIR($(BNDDIR)) DFTACTGRP(*no)" > .logs/mywebapp.splf' ].join()); diff --git a/cli/test/targets.test.ts b/cli/test/targets.test.ts index 5db14bf..1208128 100644 --- a/cli/test/targets.test.ts +++ b/cli/test/targets.test.ts @@ -111,7 +111,7 @@ test(`Multi-module program and service programs`, () => { expect(srvPgms.length).toBe(1); const modules = targets.getResolvedObjects(`MODULE`); - expect(modules.length).toBe(4); + expect(modules.length).toBe(5); const webappPgm = programs[0]; expect(webappPgm.extension).toBe(`pgm`); From 4fe762f1cbc8169c4844b316974a060f64ef2b16 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 3 Nov 2023 14:59:09 -0400 Subject: [PATCH 10/10] Updates to the docs --- docs/pages/cli/index.md | 2 +- docs/pages/cli/make.md | 19 ++++++++++++++----- docs/pages/general/rules.md | 4 +++- docs/pages/general/structure.md | 2 +- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/docs/pages/cli/index.md b/docs/pages/cli/index.md index 31fa708..e18dd6d 100644 --- a/docs/pages/cli/index.md +++ b/docs/pages/cli/index.md @@ -30,7 +30,7 @@ Types available: * This is particularly useful for pull-requests. It is possible have a pipeline that runs on a push to a branch/PR to generate dependency information. * See an [example report here](https://github.com/worksofliam/ibmi-company_system-rmake/actions/runs/5765430282). See about [GitHub Actions](./pages/cli/gha.md) here. -``` +```sh so -bf imd -l `git diff --name-only origin/main origin/${GITHUB_HEAD_REF}` ``` diff --git a/docs/pages/cli/make.md b/docs/pages/cli/make.md index 070dfd0..401448b 100644 --- a/docs/pages/cli/make.md +++ b/docs/pages/cli/make.md @@ -2,9 +2,18 @@ This page describes information about the `makefile` that is generated for a pro This section only applies if you use `-bf make`. +## `iproj.json` properties + +Source Orbit makes use of a few properties that you can put inside your `iproj.json` file. + +| 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. Service programs will be added to the binding directory automatically and programs automatically include them. No need to use the `BNDDIR` header. Be careful, because changing the custom compile commands from the default may break this functionality. +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. ## Changing compile options @@ -22,14 +31,14 @@ interface CompileData { becomes: ObjectType; /** will copy the source to a temp member first */ member?: boolean, - /** `commands` do not respect the library list and is run before 'command' */ - commands?: string[] + /** `preCommands` do not respect the library list and is run before 'command' */ + preCommands?: string[] /** `command` does respect the library list */ command?: string; /** Used if the commands are built up from source. Usually means `command` and `commands` is blank */ commandSource?: boolean; - /** if the non-source object now requires source. Use make generic name like `qbndsrc/%.bnd` */ - targetSource?: string; + /** `postCommands` do not respect the library list and is run after 'command' */ + postCommands?: string[] }; ``` diff --git a/docs/pages/general/rules.md b/docs/pages/general/rules.md index 8f99198..0be6524 100644 --- a/docs/pages/general/rules.md +++ b/docs/pages/general/rules.md @@ -44,8 +44,10 @@ Source Orbit does not care about project structure, but does enforce these rules * if your source includes `.pgm`, then it will become a program. * `mypgm.pgm.rpgle` becomes a `MYPGM.PGM` object + * If you want to use multi-module programs, go ahead and add the prototypes to export functions/procedures in other modules and Source Orbit will automatically take care of the binding. * if your source does not include `.pgm`, then it will become a module, cmd, dtaara, etc. -* assumes binder source (`.bnd`/`.binder`) is a service program. Source Orbit will scan the binder source to find the exported functions/procedures from modules inside the project. +* assumes binder source (`.bnd`/`.binder`) is a service program. + * Source Orbit will scan the binder source to find the exported functions/procedures from modules inside the project. * Source Orbit does not yet support SQL long name references. (Coming soon) ### SQL sources diff --git a/docs/pages/general/structure.md b/docs/pages/general/structure.md index 1b2ebd4..b50e18e 100644 --- a/docs/pages/general/structure.md +++ b/docs/pages/general/structure.md @@ -44,7 +44,7 @@ While it is possible to use `INCDIR` and then not provide a directory on the inc If you want your local project to resolve files on the IFS, make sure you specify your 'include directories' * by using the `INCDIR` parameter available on most ILE compilers, -* or inside the [`iproj.json` file with the `includePath` property](https://ibm.github.io/vscode-ibmi-projectexplorer/#/pages/ibm-i-projects/iproj-json?id=includepath) to the paths where the compiler should look up (this is supported by ibmi-bob) +* or inside the [`iproj.json` file with the `includePaths` property](https://ibm.github.io/vscode-ibmi-projectexplorer/#/pages/ibm-i-projects/iproj-json?id=includepath) to the paths where the compiler should look up (this is supported by ibmi-bob) ## Example project structure