Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate binder source from module or service program #2485

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
16 changes: 16 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1699,6 +1699,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 @@ -2312,6 +2319,10 @@
{
"command": "code-for-ibmi.searchIFSBrowser",
"when": "never"
},
{
"command": "code-for-ibmi.generateBinderSource",
"when": "never"
}
],
"view/title": [
Expand Down Expand Up @@ -2932,6 +2943,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).*/",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may want to escape the dot here, since you'll be looking at an actual dot (and not any character).

Suggested change
"when": "view == objectBrowser && viewItem =~ /^object.(module|srvpgm).*/",
"when": "view == objectBrowser && viewItem =~ /^object\.(module|srvpgm).*/",

"group": "1_objActions@6"
}
],
"explorer/context": [
Expand Down
61 changes: 60 additions & 1 deletion src/api/IBMiContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import tmp from 'tmp';
import util from 'util';
import * as node_ssh from "node-ssh";
import { GetMemberInfo } from './components/getMemberInfo';
import { AttrOperands, CommandResult, IBMiError, IBMiMember, IBMiObject, IFSFile, QsysPath, SpecialAuthorities } from './types';
import { AttrOperands, CommandResult, IBMiError, IBMiMember, IBMiObject, IFSFile, ProgramExportImportInfo, ModuleExport, QsysPath, SpecialAuthorities } from './types';
import { FilterType, parseFilter, singleGenericName } from './Filter';
import { default as IBMi } from './IBMi';
import { Tools } from './Tools';
Expand Down Expand Up @@ -632,6 +632,65 @@ 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")
);
if (results.length) {
return results.map(result => ({
program_library: result.PROGRAM_LIBRARY,
program_name: result.PROGRAM_NAME,
object_type: result.OBJECT_TYPE,
symbol_name: result.SYMBOL_NAME,
symbol_usage: result.SYMBOL_USAGE,
argument_optimization: result.ARGUMENT_OPTIMIZATION,
data_item_size: result.DATA_ITEM_SIZE
} as ProgramExportImportInfo));
} else {
return [];
}
Comment on lines +652 to +664
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to check the array's length. Directly return the map operation. Map on an empty array is an empty array, so you'll be good anyway.

Suggested change
if (results.length) {
return results.map(result => ({
program_library: result.PROGRAM_LIBRARY,
program_name: result.PROGRAM_NAME,
object_type: result.OBJECT_TYPE,
symbol_name: result.SYMBOL_NAME,
symbol_usage: result.SYMBOL_USAGE,
argument_optimization: result.ARGUMENT_OPTIMIZATION,
data_item_size: result.DATA_ITEM_SIZE
} as ProgramExportImportInfo));
} else {
return [];
}
return results.map(result => ({
program_library: result.PROGRAM_LIBRARY,
program_name: result.PROGRAM_NAME,
object_type: result.OBJECT_TYPE,
symbol_name: result.SYMBOL_NAME,
symbol_usage: result.SYMBOL_USAGE,
argument_optimization: result.ARGUMENT_OPTIMIZATION,
data_item_size: 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")
);
if (results.length) {
return results.map(result => ({
module_library: result.MODULE_LIBRARY,
module_name: result.MODULE_NAME,
module_attr: result.MODULE_ATTR,
symbol_name: result.SYMBOL_NAME,
symbol_type: result.SYMBOL_TYPE,
argument_optimization: result.ARGUMENT_OPTIMIZATION
} as ModuleExport));
}
return [];
Comment on lines +681 to +691
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, return the map directly. Runnin map on an empty array won't hurt.

}

/**
*
* @param filter: the criterias used to list the members
Expand Down
74 changes: 74 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 { Tools } from '../../Tools';
import { posix } from 'path';
import IBMi from '../../IBMi';
import { newConnection, disposeConnection, CONNECTION_TIMEOUT } from '../connection';
import { ModuleExport, ProgramExportImportInfo } from '../../types';

describe('Content Tests', {concurrent: true}, () => {
let connection: IBMi
Expand Down Expand Up @@ -642,4 +643,77 @@ 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: string = `${Tools.makeid().toUpperCase()}`;
const source: string = `/tmp/vscodetemp-${id}.clle`;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest to use connection.withTempDirectory here to handle the creation/cleanup of temporary IFS files (see above). You don't have to bother with DEL afterwards.

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)?.symbol_name).toBe(id);

await connection!.runCommand({
command: `DLTMOD MODULE(${tempLib}/${id})`,
environment: 'ile'
});
Comment on lines +670 to +673
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be put in a try/finally{} block, to make sure objects are cleaned up unconditionally.

await connection!.runCommand({
command: `DEL OBJLNK('${source}')`,
environment: 'ile'
});
});

it('getProgramExportImportInfo', async () => {
const content = connection.getContent();
const config = connection.getConfig();
const tempLib = config!.tempLibrary;
const id: string = `${Tools.makeid().toUpperCase()}`;
const source: string = `/tmp/vscodetemp-${id}.clle`;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, try using connection.withTempDirectory for this.

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.symbol_usage === '*PROCEXP');

expect(info.length).toBe(1);
expect(info.at(0)?.symbol_name).toBe(id);

await connection!.runCommand({
command: `DLTSRVPGM SRVPGM(${tempLib}/${id})`,
environment: 'ile'
});
await connection!.runCommand({
command: `DLTMOD MODULE(${tempLib}/${id})`,
environment: 'ile'
});
Comment on lines +706 to +713
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be put in a try/finally{} block, to make sure objects are cleaned up unconditionally.

await connection!.runCommand({
command: `DEL OBJLNK('${source}')`,
environment: 'ile'
});
});
});
19 changes: 19 additions & 0 deletions src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,4 +182,23 @@ export type SearchHitLine = {
content: string
}

export interface ProgramExportImportInfo {
program_library: string,
program_name: string,
object_type: string,
symbol_name: string,
symbol_usage: string,
argument_optimization: string,
data_item_size: number
}

export interface ModuleExport {
module_library: string,
module_name: string,
module_attr: string,
symbol_name: string,
symbol_type: string,
argument_optimization: string,
}
Comment on lines +185 to +202
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer camel case for variable names, to keep it consistent with the others.

Suggested change
export interface ProgramExportImportInfo {
program_library: string,
program_name: string,
object_type: string,
symbol_name: string,
symbol_usage: string,
argument_optimization: string,
data_item_size: number
}
export interface ModuleExport {
module_library: string,
module_name: string,
module_attr: string,
symbol_name: string,
symbol_type: string,
argument_optimization: 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.symbol_type === 'PROCEDURE');
} else {
exports = (await contentApi.getProgramExportImportInfo(node.object.library, node.object.name, node.object.type))
.filter(info => info.symbol_usage === '*PROCEXP');
}
const content = [
`/* Binder source generated from ${node} */`,
``,
worksofliam marked this conversation as resolved.
Show resolved Hide resolved
`STRPGMEXP PGMLVL(*CURRENT) /* SIGNATURE("") */`,
...exports.map(info => ` EXPORT SYMBOL("${info.symbol_name}")`),
`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
Loading