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

Plugin API Surface Refactor #879

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

ianmarmour
Copy link

@ianmarmour ianmarmour commented Jan 21, 2025

Description of the change

These changes clean up the interfaces around code generation and plugins so that they will be ready to spin off into their own crate without dramatically increasing the API surface of the new crate. The overview of the changes made are below,

  • Plugin no longer has a Default implementation.
  • Plugin is now a wrapper around a Vec<u8>.
  • Plugin now stores it's kind which can only be provided when the plugin-internal feature is set.
  • Plugin no longer provides a config_schema function to generate the config_schema (this is now created from a plugin instead and will stay within the CLI crate)
  • Adds feature flag plugin-internal used to disable/enable internal plugin generation related features (V2 Access, JS Config Support, ETC...) should always be enabled for the CLI crate.
  • Generator's js_runtime_config is now passed in as an optional byte array.

I tried to completely remove the concept of an internal user plugin but I found that I couldn't get rid of the need for a distinction between an external plugin and an internal one without breaking the ability to support using JS configs with internal plugins. I think there's a handful of different approach to solve this but ultimately I feel like the feature-flag gated enums are an equally valid option compared to the suggested boolean with setters.

Why am I making this change?

To lower the API surface of the plugin/code-generation components in preparation for a future split of that logic into a javy-codegen crate based on @jeffcharles request.

Checklist

  • I've updated the relevant CHANGELOG files if necessary. Changes to javy-cli and javy-plugin do not require updating CHANGELOG files.
  • I've updated the relevant crate versions if necessary. Versioning policy for library crates
  • I've updated documentation including crate documentation if necessary.

@ianmarmour ianmarmour changed the title Generator refactor Plugin API Surface Refactor Jan 21, 2025
@ianmarmour ianmarmour marked this pull request as ready for review January 21, 2025 14:42
@jeffcharles jeffcharles self-requested a review January 21, 2025 15:03
Copy link
Collaborator

@jeffcharles jeffcharles left a comment

Choose a reason for hiding this comment

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

Thanks for the work! I understand it takes time to do and I appreciate you taking the time to do it.

That said, I do have concerns about what's in here. I think there was some misunderstanding around the abstractions I would've liked to see for the Plugin. Specifically, I wanted a richer Plugin struct/abstraction for use in the CLI crate and a different and very slimmed down Plugin struct/abstraction for use in the future codegen crate (specifically in the Generator) with additional properties and gated setters on the Generator to preserve behaviour with a very slimmed down plugin abstraction.

Does that make sense?

#[cfg(feature = "plugin-internal")]
impl CodeGenBuilder {
/// Build a [`CodeGenerator`].
pub fn build(self, ty: CodeGenType, js_runtime_config: Vec<u8>) -> Result<Generator> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we make js_runtime_config a setter method instead of an argument?

pub fn wit_opts(&mut self, opts: WitOptions) -> &mut Self {
self.wit_opts = opts;
self
pub fn new(plugin: Plugin, wit_opts: WitOptions, source_compression: bool) -> Self {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we keep the existing builder interface please?

#[cfg(feature = "plugin-internal")]
impl Generator {
/// Creates a new [`Generator`].
pub(crate) fn new(ty: CodeGenType, js_runtime_config: Vec<u8>, plugin: Plugin) -> Self {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Similar feedback here, can we make this a builder interface instead of having a new function with parameters? That way we can just gate the setter for the js_runtime_config with the Cargo feature instead of having two different versions of new.

/// Generate the starting module.
fn generate_initial_module(&self) -> Result<Module> {
let config = transform::module_config();
let module = match &self.ty {
CodeGenType::Static => {
// Set runtime config to either a valid runtime config or null bytes.
let runtime_config = self.js_runtime_config.clone().unwrap_or_default();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: Can we just have the field start as an empty Vec<u8> instead of an Option<Vec<u8>>? That might simplify things a bit.

/// Optional path to Javy plugin Wasm module. Not supported for
/// dynamically linked modules. JavaScript config options are also not
/// supported when using this parameter.
/// Optional path to Javy plugin Wasm module. Required for dynamically
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm going to open a separate PR with this part of the change since this was an oversight of mine that I should correct ASAP.

Comment on lines +241 to +244
let eval_bytecode_fn_id = if matches!(
self.plugin.kind(),
PluginKind::Internal(InternalPluginKind::V2)
) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I was expecting something following along the lines of this feedback in #877 (comment):

Update Generator to no longer reference Plugin but instead reference a wrapper type around a byte slice and have methods that set booleans on self for whether it's linking to the v2 plugin or linking to the default plugin.

So this would look more like:

let eval_bytecode_fn_id = if self.linking_v2_plugin {

With a linking_v2_plugin boolean field set to false defined in Generator along with a method that looks like:

#[cfg(feature = "plugin-internal")]
pub fn linking_v2_plugin(&mut self, value: bool) {
    self.linking_v2_plugin = value;
}

if !self.plugin.is_user_plugin() {

// Only internal plugins expose eval_bytecode function.
if matches!(self.plugin.kind(), PluginKind::Internal(..)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Similarly here, I was hoping this would look more like:

if self.linking_v2_plugin || self.linking_default_plugin {

with a similar setter for linking_default_plugin that we could gate behind a feature.

Comment on lines +52 to +56
/// Represents any valid Javy plugin.
#[derive(Clone, Debug)]
pub(crate) struct Plugin {
pub(crate) bytes: Vec<u8>,
kind: PluginKind,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Instead of this, can we do:

Suggested change
/// Represents any valid Javy plugin.
#[derive(Clone, Debug)]
pub(crate) struct Plugin {
pub(crate) bytes: Vec<u8>,
kind: PluginKind,
/// Javy plugin.
pub struct Plugin(Vec<u8>);
// Represents any valid Javy plugin.
#[derive(Clone, Debug)]
pub(crate) struct CliPlugin {
pub(crate) bytes: Plugin,
kind: PluginKind,

With the idea that the Plugin struct will live in the codegen crate and the CliPlugin will live in the CLI crate the Generator will reference Plugin. Basically I want to keep the PluginKind knowledge entirely in the CLI crate and use a couple feature gated booleans to get Generator to do what it needs to do rather than use a richer plugin abstraction in the generator so it's straightforward for a codegen user to just pass their plugin bytes along without having to worry about PluginKinds.

dump_wat = ["dep:wasmprinter"]
plugin-internal = []
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't want to introduce the feature gates in this PR but rather when the codegen crate is created. That said, it would be helpful to mark off with PR comments as to which methods would be feature gated.

}

impl ConfigSchema {
pub(crate) fn from_plugin(plugin: &Plugin) -> Result<Option<ConfigSchema>> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you can keep this method on CliPlugin, just not the codegen plugin.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants