Open Thevakumar-Luheerathan opened 3 months ago
Adding a tool validation logic (validateTool
method) to the PackCommand
can resolve this issue.
package io.ballerina.cli.cmd;
public class PackCommand implements BLauncherCmd {
// ...
@Override
public void execute() {
// ...
// Validate tool and sub-command existence
try {
validateTool(project);
}catch (ServiceConfigurationError | IOException e) {
CommandUtil.printError(this.errStream, e.getMessage(), null, false);
CommandUtil.exitError(this.exitWhenFinish);
return;
}
TaskExecutor taskExecutor = new TaskExecutor.TaskBuilder()
// ...
}
private void validateTool(Project project) throws ServiceConfigurationError, IOException {
// Get the BalTools.toml content
Optional<BalToolDescriptor> balToolDescriptor = project.currentPackage().manifest().balToolDescriptor();
if(balToolDescriptor.isPresent()){
BalToolDescriptor balToolManifest = balToolDescriptor.get();
String id = balToolManifest.tool().getId();
List<URL> jarURLs = new ArrayList<>();
List<String> toolDependencyJARs = balToolManifest.getBalToolDependencies();
toolDependencyJARs.forEach(dependencyJAR ->{
Path jarPath = Path.of(dependencyJAR);
if(Files.exists(jarPath)){
// Convert the paths to the URL Format
try {
jarURLs.add(jarPath.toUri().toURL());
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
});
URLClassLoader ucl = new URLClassLoader(jarURLs.toArray(new URL[0]));
// Validate BLauncherCmd JARs
validateBlauncherTool(id, ucl, jarURLs);
// Validate CodeGeneratorTool JARs
validateCodeGeneratorTool(id, ucl, jarURLs);
}
}
private void validateBlauncherTool(String toolId,URLClassLoader ucl, List<URL> jarURLs){
List<String> allCommandNames = new ArrayList<>();
List<String> subCommandNames = new ArrayList<>();
// Load and check class which implements BLauncherCmd interface
ServiceLoader<BLauncherCmd> customBlauncherCmds = ServiceLoader.load(BLauncherCmd.class, ucl);
customBlauncherCmds.forEach(customCmd ->{
if(jarURLs.contains(customCmd.getClass().getProtectionDomain().getCodeSource().getLocation())){
// Retrieve annotation of the command
CommandLine.Command commandAnnotation = customCmd.getClass()
.getAnnotation(CommandLine.Command.class);
// Validate if main command follows expected pattern
boolean mainCommandValidity = validateCommandName(commandAnnotation.name());
if(!mainCommandValidity){
CommandUtil.printError(this.errStream, "invalid command name format for command '" +
commandAnnotation.name() + "'",null, false);
CommandUtil.exitError(this.exitWhenFinish);
}
allCommandNames.add(commandAnnotation.name());
// Retrieve the sub commands
Class<?>[] subcommands = commandAnnotation.subcommands();
List<Class<?>> list = Arrays.stream(subcommands).toList();
list.forEach(subcommand ->{
// Retrieve annotation of the command
Class<? extends BLauncherCmd> subCmdClass = (Class<? extends BLauncherCmd>) subcommand;
CommandLine.Command subCommandAnnotation = subCmdClass.getAnnotation(CommandLine.Command.class);
// Validate if sub command follows expected pattern
boolean subCommandValidity = validateCommandName(subCommandAnnotation.name());
if(!subCommandValidity){
CommandUtil.printError(this.errStream, "invalid command name format for command '" +
subCommandAnnotation.name() + "'",null, false);
CommandUtil.exitError(this.exitWhenFinish);
}
subCommandNames.add(subCommandAnnotation.name());
});
}
});
if(toolId != null){
allCommandNames.forEach(commandName ->{
if(!subCommandNames.contains(commandName) && !toolId.equals(commandName)){
CommandUtil.printError(this.errStream, "command name '" + commandName +
"' does not match id '" + toolId + "' provided in " + ProjectConstants.BAL_TOOL_TOML,
null, false);
CommandUtil.exitError(this.exitWhenFinish);
}
});
}
}
private void validateCodeGeneratorTool(String toolId,URLClassLoader ucl, List<URL> jarURLs){
List<String> allCommandNames = new ArrayList<>();
List<String> subCommandNames = new ArrayList<>();
// Load and check class which implements CodeGeneratorTool interface
ServiceLoader<CodeGeneratorTool> customBlauncherCmds = ServiceLoader.load(CodeGeneratorTool.class, ucl);
customBlauncherCmds.forEach(customCmd ->{
if(jarURLs.contains(customCmd.getClass().getProtectionDomain().getCodeSource().getLocation())){
// Retrieve annotation of the command
ToolConfig commandAnnotation = customCmd.getClass().getAnnotation(ToolConfig.class);
// Validate if main command follows expected pattern
boolean mainCommandValidity = validateCommandName(commandAnnotation.name());
if(!mainCommandValidity){
CommandUtil.printError(this.errStream, "invalid command name format for command '" +
commandAnnotation.name() + "'",null, false);
CommandUtil.exitError(this.exitWhenFinish);
}
allCommandNames.add(commandAnnotation.name());
// Retrieve the sub commands
Class<?>[] subcommands = commandAnnotation.subcommands();
List<Class<?>> list = Arrays.stream(subcommands).toList();
list.forEach(subcommand ->{
// Retrieve annotation of the command
Class<? extends CodeGeneratorTool> subCmdClass = (Class<? extends CodeGeneratorTool>) subcommand;
ToolConfig subCommandAnnotation = subCmdClass.getAnnotation(ToolConfig.class);
// Validate if sub command follows expected pattern
boolean subCommandValidity = validateCommandName(subCommandAnnotation.name());
if(!subCommandValidity){
CommandUtil.printError(this.errStream, "invalid command name format for command '" +
subCommandAnnotation.name() + "'",null, false);
CommandUtil.exitError(this.exitWhenFinish);
}
subCommandNames.add(subCommandAnnotation.name());
});
}
});
if(toolId != null){
allCommandNames.forEach(commandName ->{
if(!subCommandNames.contains(commandName) && !toolId.equals(commandName)){
CommandUtil.printError(this.errStream, "command name '" + commandName +
"' does not match id '" + toolId + "' provided in " + ProjectConstants.BAL_TOOL_TOML,
null, false);
CommandUtil.exitError(this.exitWhenFinish);
}
});
}
}
private boolean validateCommandName(String cmdName) {
boolean commandValidity = true;
if (cmdName == null || cmdName.isEmpty()) {
commandValidity = false;
} else if (!cmdName.matches("^\\w+$")) {
commandValidity = false;
} else if (cmdName.startsWith("_")) {
commandValidity = false;
} else if (cmdName.endsWith("_")) {
commandValidity = false;
} else if (cmdName.contains("__")) {
commandValidity = false;
}
return commandValidity;
}
}
For testing the solution:
Description
The above validation is needed for the following scenarios. There may be more scenarios as well.; 1) A tool can be mistakenly packed without a main command. And, it can be pushed to the Central.
Tool is not found
error is shown while using the tool.2) We need to specify commands, and subcommands of a tool in
io.ballerina.projects.buildtools.CodeGeneratorTool
file. If the actual commands do not match with the entries in the file, a bad-sad error is thrown as follows during the usage.It is better if we can validate a tool while packing.
Describe your problem(s)
No response
Describe your solution(s)
No response
Related area
-> Compilation
Related issue(s) (optional)
No response
Suggested label(s) (optional)
No response
Suggested assignee(s) (optional)
No response