Skip to content

Commit

Permalink
Merge pull request #2485 from janfh/feature/binder_source
Browse files Browse the repository at this point in the history
Generate binder source from module or service program
  • Loading branch information
worksofliam authored Feb 20, 2025
2 parents 6e296ce + e43b192 commit 3591bd2
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 2 deletions.
16 changes: 16 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1686,6 +1686,13 @@
"category": "IBM i",
"icon": "$(debug)",
"enablement": "code-for-ibmi:testing"
},
{
"command": "code-for-ibmi.generateBinderSource",
"title": "Generate binder source",
"category": "IBM i",
"icon": "$(plus)",
"enablement": "code-for-ibmi:connected"
}
],
"keybindings": [
Expand Down Expand Up @@ -2299,6 +2306,10 @@
{
"command": "code-for-ibmi.searchIFSBrowser",
"when": "never"
},
{
"command": "code-for-ibmi.generateBinderSource",
"when": "never"
}
],
"view/title": [
Expand Down Expand Up @@ -2919,6 +2930,11 @@
"command": "code-for-ibmi.debug.setup.local",
"when": "!code-for-ibmi:debugManaged && view == ibmiDebugBrowser && viewItem =~ /^certificateIssue_localissue$/",
"group": "inline"
},
{
"command": "code-for-ibmi.generateBinderSource",
"when": "view == objectBrowser && viewItem =~ /^object.(module|srvpgm).*/",
"group": "1_objActions@6"
}
],
"explorer/context": [
Expand Down
54 changes: 53 additions & 1 deletion src/api/IBMiContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { default as IBMi } from './IBMi';
import { Tools } from './Tools';
import { GetMemberInfo } from './components/getMemberInfo';
import { ObjectTypes } from './import/Objects';
import { AttrOperands, CommandResult, IBMiError, IBMiMember, IBMiObject, IFSFile, QsysPath, SpecialAuthorities } from './types';
import { AttrOperands, CommandResult, IBMiError, IBMiMember, IBMiObject, IFSFile, ModuleExport, ProgramExportImportInfo, QsysPath, SpecialAuthorities } from './types';
const tmpFile = util.promisify(tmp.file);
const readFileAsync = util.promisify(fs.readFile);
const writeFileAsync = util.promisify(fs.writeFile);
Expand Down Expand Up @@ -644,6 +644,58 @@ export default class IBMiContent {
});
}

/**
* @param object IBMiObject to get export and import info for
* @returns an array of ProgramExportImportInfo
*/
async getProgramExportImportInfo(library: string, name: string, type: string): Promise<ProgramExportImportInfo[]> {
if (!['*PGM', '*SRVPGM'].includes(type)) {
return [];
}
const results = await this.ibmi.runSQL(
[
`select PROGRAM_LIBRARY, PROGRAM_NAME, OBJECT_TYPE, SYMBOL_NAME, SYMBOL_USAGE,`,
` ARGUMENT_OPTIMIZATION, DATA_ITEM_SIZE`,
`from qsys2.program_export_import_info`,
`where program_library = '${library}' and program_name = '${name}' `,
`and object_type = '${type}'`
].join("\n")
);
return results.map(result => ({
programLibrary: result.PROGRAM_LIBRARY,
programName: result.PROGRAM_NAME,
objectType: result.OBJECT_TYPE,
symbolName: result.SYMBOL_NAME,
symbolUsage: result.SYMBOL_USAGE,
argumentOptimization: result.ARGUMENT_OPTIMIZATION,
dataItemSize: result.DATA_ITEM_SIZE
} as ProgramExportImportInfo));
}

/**
* @param object IBMiObject to get module exports for
* @returns an array of ModuleExport
*/
async getModuleExports(library: string, name: string): Promise<ModuleExport[]> {
const outfile: string = Tools.makeid().toUpperCase();
const results = await this.runStatements(
`@DSPMOD MODULE(${library}/${name}) DETAIL(*EXPORT) OUTPUT(*OUTFILE) OUTFILE(QTEMP/${outfile})`,
[
`select EXLBNM as MODULE_LIBRARY, EXMONM as MODULE_NAME, EXMOAT as MODULE_ATTR, EXSYNM as SYMBOL_NAME,`,
` case EXSYTY when '0' then 'PROCEDURE' when '1' then 'DATA' end as SYMBOL_TYPE, EXOPPP as ARGUMENT_OPTIMIZATION`,
` from QTEMP.${outfile}`
].join("\n")
);
return results.map(result => ({
moduleLibrary: result.MODULE_LIBRARY,
moduleName: result.MODULE_NAME,
moduleAttr: result.MODULE_ATTR,
symbolName: result.SYMBOL_NAME,
symbolType: result.SYMBOL_TYPE,
argumentOptimization: result.ARGUMENT_OPTIMIZATION
} as ModuleExport));
}

/**
*
* @param filter: the criterias used to list the members
Expand Down
75 changes: 75 additions & 0 deletions src/api/tests/suites/content.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import IBMi from '../../IBMi';
import { Tools } from '../../Tools';
import { CONNECTION_TIMEOUT, disposeConnection, newConnection } from '../connection';
import { ModuleExport, ProgramExportImportInfo } from '../../types';

describe('Content Tests', {concurrent: true}, () => {
let connection: IBMi
Expand Down Expand Up @@ -642,4 +643,78 @@ describe('Content Tests', {concurrent: true}, () => {
throw new Error(`Failed to create schema "${longName}"`);
}
});

it('getModuleExport', async () => {
const content = connection.getContent();
const config = connection.getConfig();
const tempLib = config!.tempLibrary;
const id = `${Tools.makeid().toUpperCase()}`;
await connection.withTempDirectory(async directory => {
const source = `${directory}/vscodetemp-${id}.clle`;
console.log(source);
try {
await content.runStatements(
`CALL QSYS2.IFS_WRITE(PATH_NAME =>'${source}',
LINE => 'PGM',
OVERWRITE => 'NONE',
END_OF_LINE => 'CRLF')`,
`CALL QSYS2.IFS_WRITE(PATH_NAME =>'${source}',
LINE => 'ENDPGM',
OVERWRITE => 'APPEND',
END_OF_LINE => 'CRLF')`,
`@CRTCLMOD MODULE(${tempLib}/${id}) SRCSTMF('${source}')`,
`select 1 from sysibm.sysdummy1`
);
let exports: ModuleExport[] = await content.getModuleExports(tempLib, id);

expect(exports.length).toBe(1);
expect(exports.at(0)?.symbolName).toBe(id);
} finally {
await connection!.runCommand({
command: `DLTMOD MODULE(${tempLib}/${id})`,
environment: 'ile'
});
}
});
});

it('getProgramExportImportInfo', async () => {
const content = connection.getContent();
const config = connection.getConfig();
const tempLib = config!.tempLibrary;
const id = `${Tools.makeid().toUpperCase()}`;
await connection.withTempDirectory(async directory => {
const source = `${directory}/vscodetemp-${id}.clle`;
try {
await content.runStatements(
`CALL QSYS2.IFS_WRITE(PATH_NAME =>'${source}',
LINE => 'PGM',
OVERWRITE => 'NONE',
END_OF_LINE => 'CRLF')`,
`CALL QSYS2.IFS_WRITE(PATH_NAME =>'${source}',
LINE => 'ENDPGM',
OVERWRITE => 'APPEND',
END_OF_LINE => 'CRLF')`,
`@CRTCLMOD MODULE(${tempLib}/${id}) SRCSTMF('${source}')`,
`@CRTSRVPGM SRVPGM(${tempLib}/${id}) MODULE(${tempLib}/${id}) EXPORT(*ALL)`,
`select 1 from sysibm.sysdummy1`
);

const info: ProgramExportImportInfo[] = (await content.getProgramExportImportInfo(tempLib, id, '*SRVPGM'))
.filter(info => info.symbolUsage === '*PROCEXP');

expect(info.length).toBe(1);
expect(info.at(0)?.symbolName).toBe(id);
} finally {
await connection!.runCommand({
command: `DLTSRVPGM SRVPGM(${tempLib}/${id})`,
environment: 'ile'
});
await connection!.runCommand({
command: `DLTMOD MODULE(${tempLib}/${id})`,
environment: 'ile'
});
}
});
});
});
19 changes: 19 additions & 0 deletions src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,4 +189,23 @@ export interface AspInfo {
rdbName: string;
}

export interface ProgramExportImportInfo {
programLibrary: string,
programName: string,
objectType: string,
symbolName: string,
symbolUsage: string,
argumentOptimization: string,
dataItemSize: number
}

export interface ModuleExport {
moduleLibrary: string,
moduleName: string,
moduleAttr: string,
symbolName: string,
symbolType: string,
argumentOptimization: string,
}

export * from "./configuration/config/types";
23 changes: 22 additions & 1 deletion src/ui/views/objectBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Search } from "../../api/Search";
import { Tools } from "../../api/Tools";
import { getMemberUri } from "../../filesystems/qsys/QSysFs";
import { instance } from "../../instantiate";
import { CommandResult, DefaultOpenMode, FilteredItem, FocusOptions, IBMiMember, IBMiObject, MemberItem, OBJECT_BROWSER_MIMETYPE, ObjectFilters, ObjectItem, WithLibrary } from "../../typings";
import { CommandResult, DefaultOpenMode, FilteredItem, FocusOptions, IBMiMember, IBMiObject, MemberItem, ModuleExport, OBJECT_BROWSER_MIMETYPE, ObjectFilters, ObjectItem, ProgramExportImportInfo, WithLibrary } from "../../typings";
import { editFilter } from "../../webviews/filters";
import { VscodeTools } from "../Tools";
import { BrowserItem, BrowserItemParameters } from "../types";
Expand Down Expand Up @@ -541,6 +541,27 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) {
objectTreeViewer.reveal(item, options);
}),

vscode.commands.registerCommand(`code-for-ibmi.generateBinderSource`, async (node: ObjectBrowserObjectItem) => {
const contentApi = getContent();
let exports: ProgramExportImportInfo[] | ModuleExport[] = [];
if (node.object.type === '*MODULE') {
exports = (await contentApi.getModuleExports(node.object.library, node.object.name))
.filter(exp => exp.symbolType === 'PROCEDURE');
} else {
exports = (await contentApi.getProgramExportImportInfo(node.object.library, node.object.name, node.object.type))
.filter(info => info.symbolUsage === '*PROCEXP');
}
const content = [
`/* Binder source generated from ${node} */`,
``,
`STRPGMEXP PGMLVL(*CURRENT) /* SIGNATURE("") */`,
...exports.map(info => ` EXPORT SYMBOL("${info.symbolName}")`),
`ENDPGMEXP`,
].join("\n");
const textDoc = await vscode.workspace.openTextDocument({ language: 'bnd', content });
await vscode.window.showTextDocument(textDoc);
}),

vscode.commands.registerCommand(`code-for-ibmi.createMember`, async (node: ObjectBrowserSourcePhysicalFileItem, fullName?: string) => {
const connection = getConnection();
const toPath = (value: string) => connection.upperCaseName(`${node.path}/${value}`);
Expand Down

0 comments on commit 3591bd2

Please sign in to comment.