Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,7 @@ jobs:
needs:
- build-and-store
- build-golem-ts
- build-golem-scala
- list-test-component-groups
strategy:
fail-fast: false
Expand Down Expand Up @@ -804,6 +805,55 @@ jobs:
wasi-sdk-path: ${{ env.WASI_SDK_PATH }}
wasm-rquickjs-version: ${{ env.WASM_RQUICKJS_VERSION }}

- name: Setup WASM build tools for Scala components
if: matrix.chunk.needs-scala
uses: ./.github/actions/setup-wasm-component-tools
with:
wasi-sdk-version: ${{ env.WASI_SDK_VERSION }}
wasi-sdk-path: ${{ env.WASI_SDK_PATH }}
wasm-rquickjs-version: ${{ env.WASM_RQUICKJS_VERSION }}

- name: Install wasm-tools for MoonBit components
if: matrix.chunk.needs-moonbit
run: cargo binstall --force --locked wasm-tools@1.248.0

- name: Setup Java for Scala components
if: matrix.chunk.needs-scala
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 17

- name: Setup SBT for Scala components
if: matrix.chunk.needs-scala
uses: sbt/setup-sbt@v1

- name: Cache Scala dependencies
if: matrix.chunk.needs-scala
uses: coursier/cache-action@v8

- name: Restore Scala SDK ivy local repo
if: matrix.chunk.needs-scala
uses: actions/cache/restore@v5
with:
path: ~/.ivy2/local
key: scala-sdk-ivy-local-${{ github.run_id }}
fail-on-cache-miss: true

- name: Setup MoonBit
if: matrix.chunk.needs-moonbit
uses: hustcer/setup-moonbit@v1
with:
version: ${{ env.MOONBIT_INSTALL_VERSION }}
core-version: ${{ env.MOONBIT_INSTALL_VERSION }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Update MoonBit fixture dependencies
if: matrix.chunk.needs-moonbit
working-directory: test-components/tool-streaming-moonbit
run: moon update

- name: Install TS SDK dependencies
if: matrix.chunk.needs-node
working-directory: sdks/ts
Expand All @@ -819,9 +869,16 @@ jobs:
run: ./build-components.sh ${{ matrix.chunk.name }}

- name: Collect built WASMs
env:
EXPECTED_ARTIFACT: ${{ matrix.chunk.expected-artifact }}
run: |
mkdir -p tmp/test-components-wasm
cp test-components/*.wasm tmp/test-components-wasm/ 2>/dev/null || true
if [[ -n "$EXPECTED_ARTIFACT" ]]; then
test -s "test-components/$EXPECTED_ARTIFACT"
cp "test-components/$EXPECTED_ARTIFACT" tmp/test-components-wasm/
else
cp test-components/*.wasm tmp/test-components-wasm/ 2>/dev/null || true
fi
count=$(find tmp/test-components-wasm -maxdepth 1 -name '*.wasm' | wc -l)
echo "Built $count WASM files in chunk ${{ matrix.chunk.name }}"
test "$count" -gt 0
Expand Down
135 changes: 44 additions & 91 deletions cli/golem-cli/src/bridge_gen/moonbit/tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,18 +134,6 @@ supported_targets = "+wasm"
writer.line("// Generated by golem-cli. Do not edit.");
writer.blank();

writer.line("async fn drop_tool_stdout(stdout : @asyncCore.Stream[Byte]?) -> Unit {");
writer.indent();
writer.line("match stdout {");
writer.indent();
writer.line("Some(stream) => stream.drop()");
writer.line("None => ()");
writer.dedent();
writer.line("}");
writer.dedent();
writer.line("}");
writer.blank();

self.client_items(&mut writer)?;
self.error_items(&mut writer)?;
self.inner.write_type_definitions(&mut writer)?;
Expand Down Expand Up @@ -329,17 +317,24 @@ supported_targets = "+wasm"
};
let error_type = self.error_type(command_index, body);
let success_type = self.success_type(body)?;
let has_stdout = body.stdout.is_some();
let client_success_type = if has_stdout {
format!("@tool.TypedToolInvocation[{success_type}, {error_type}]")
} else {
success_type.clone()
};

write_doc(writer, &node);
writer.line("///|");
writer.line("#warnings(\"-unused_try\")");
writer.line(format!(
"pub async fn {}::{}(self : {}{}) -> Result[{}, @tool.ToolError[{}]] {{",
"pub {}fn {}::{}(self : {}{}) -> Result[{}, @tool.ToolError[{}]] {{",
if has_stdout { "" } else { "async " },
client.struct_name,
method_name,
client.struct_name,
format_params(&params),
success_type,
client_success_type,
error_type
));
writer.indent();
Expand Down Expand Up @@ -382,15 +377,37 @@ supported_targets = "+wasm"
.skip(1)
.collect::<Vec<_>>(),
);
let invoke = if body.errors.is_empty() {
format!("self.client.invoke_and_await_no_tool_error({path}, input, {stdin})")
let error_decoder = if body.errors.is_empty() {
"fn(_) { Err(\"remote custom tool error is not declared by this client\") }".to_string()
} else {
let decoder = error_decoder_name(
error_decoder_name(
self.error_names
.get(&command_index)
.context("missing error enum")?,
);
format!("self.client.invoke_and_await({path}, input, {stdin}, {decoder})")
)
};
if has_stdout {
writer.line(format!(
"match self.client.start({path}, input, {stdin}, true, {error_decoder}) {{"
));
writer.indent();
writer.line("Err(error) => Err(error)");
writer.line("Ok(invocation) => @tool.typed_invocation(invocation, fn(result) {");
writer.indent();
self.result_decode(writer, body)?;
writer.dedent();
writer.line("})");
writer.dedent();
writer.line("}");
writer.dedent();
writer.line("}");
return Ok(());
}

let invoke = if body.errors.is_empty() {
format!("self.client.invoke_and_await_no_tool_error({path}, input, {stdin})")
} else {
format!("self.client.invoke_and_await({path}, input, {stdin}, {error_decoder})")
};
writer.line(format!("match {invoke} {{"));
writer.indent();
Expand All @@ -412,12 +429,7 @@ supported_targets = "+wasm"
writer.line("let typed = match @tool.expect_value(result.result) {");
writer.indent();
writer.line("Ok(value) => value");
writer.line("Err(error) => {");
writer.indent();
writer.line("drop_tool_stdout(result.stdout)");
writer.line("return Err(error)");
writer.dedent();
writer.line("}");
writer.line("Err(error) => return Err(error)");
writer.dedent();
writer.line("}");
let decoded = guest_codec_source(
Expand All @@ -426,51 +438,18 @@ supported_targets = "+wasm"
);
writer.line(format!("let decoded = try {{ {decoded} }} catch{{"));
writer.indent();
writer.line("error => {");
writer.indent();
writer.line("drop_tool_stdout(result.stdout)");
writer.line("return Err(@tool.tool_protocol_error(\"failed to decode tool result: \" + repr(error)))");
writer.dedent();
writer.line("}");
writer.line("error => return Err(@tool.tool_protocol_error(\"failed to decode tool result: \" + repr(error)))");
writer.dedent();
writer.line("}");
writer.line("Ok(decoded)");
} else {
writer.line("match @tool.expect_no_value(result.result) {");
writer.indent();
writer.line("Err(error) => {");
writer.indent();
writer.line("drop_tool_stdout(result.stdout)");
writer.line("return Err(error)");
writer.dedent();
writer.line("}");
writer.line("Ok(_) => ()");
writer.line("Err(error) => Err(error)");
writer.line("Ok(_) => Ok(())");
writer.dedent();
writer.line("}");
}

match (&body.result, &body.stdout) {
(Some(_), Some(stdout)) if stdout.required => {
writer.line("match @tool.expect_stdout(result.stdout) {");
writer.indent();
writer.line("Ok(stdout) => Ok((decoded, stdout))");
writer.line("Err(error) => Err(error)");
writer.dedent();
writer.line("}");
}
(Some(_), Some(_)) => writer.line("Ok((decoded, result.stdout))"),
(Some(_), None) => {
write_drop_unexpected_stdout(writer);
writer.line("Ok(decoded)");
}
(None, Some(stdout)) if stdout.required => {
writer.line("@tool.expect_stdout(result.stdout)");
}
(None, Some(_)) => writer.line("Ok(result.stdout)"),
(None, None) => {
write_drop_unexpected_stdout(writer);
writer.line("Ok(())");
}
}
Ok(())
}

Expand All @@ -480,19 +459,7 @@ supported_targets = "+wasm"
.as_ref()
.map(|result| self.inner.type_reference_with_multimodal(&result.type_))
.transpose()?;
let stdout = body.stdout.as_ref().map(|spec| {
if spec.required {
"@asyncCore.Stream[Byte]".to_string()
} else {
"@asyncCore.Stream[Byte]?".to_string()
}
});
Ok(match (value, stdout) {
(Some(value), Some(stdout)) => format!("({value}, {stdout})"),
(Some(value), None) => value,
(None, Some(stdout)) => stdout,
(None, None) => "Unit".to_string(),
})
Ok(value.unwrap_or_else(|| "Unit".to_string()))
}

fn error_items(&self, writer: &mut MoonBitWriter) -> anyhow::Result<()> {
Expand Down Expand Up @@ -793,20 +760,6 @@ fn write_doc(writer: &mut MoonBitWriter, node: &CommandNode) {
writer.doc(&node.doc.description);
}

fn write_drop_unexpected_stdout(writer: &mut MoonBitWriter) {
writer.line("match result.stdout {");
writer.indent();
writer.line("Some(stdout) => {");
writer.indent();
writer.line("stdout.drop()");
writer.line("return Err(@tool.tool_protocol_error(\"tool result unexpectedly contained stdout stream\"))");
writer.dedent();
writer.line("}");
writer.line("None => ()");
writer.dedent();
writer.line("}");
}

fn error_variant_names(body: &CommandBody) -> Vec<String> {
unique_idents_with_reserved(
body.errors
Expand Down Expand Up @@ -1171,9 +1124,9 @@ mod tests {
let source = std::fs::read_to_string(target.join("client/client.mbt")).unwrap();
for expected in [
"pub(all) struct SearchClient",
"pub async fn SearchClient::search(",
"pub fn SearchClient::search(",
"pub fn SearchClient::admin(",
"pub async fn SearchAdminClient::reset(",
"pub fn SearchAdminClient::reset(",
"pub(all) enum SearchError",
"@tool.build_canonical_input(model, values)",
"@tool.CanonicalInputModel::{ fields:",
Expand Down
Loading
Loading