From ac8c9903e92f38e231e92183ca84d4c8cc942962 Mon Sep 17 00:00:00 2001 From: foxnne Date: Sat, 17 Oct 2026 17:59:56 -0500 Subject: [PATCH 1/2] zig: build on Zig 0.17 The mechanical pass (fizzy's scripts/zig-0.17/rewrite.sh), and by hand: the build checks and writes credentials.zon through b.root, and declares it, so an edit to it reconfigures; 0.17 caches the configuration without regard to the environment, which is now read only to write a missing one. CI on Zig 0.17.0. Part of fizzyedit/plans#5. The fizzy pin moves to the 0.17 SDK at its release; until then this builds against fizzy's zig/0.17 sdk/ by path. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yml | 2 +- .github/workflows/sdk-repin.yml | 2 +- build.zig | 8 ++++-- build.zig.zon | 2 +- plugin.zig | 1 - src/drive.zig | 1 - src/drive_test.zig | 44 ++++++++++++++++----------------- 7 files changed, 31 insertions(+), 29 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ec569275..c9a9e6e3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,6 +28,6 @@ jobs: permissions: contents: write with: - zig-version: "0.16.0" + zig-version: "0.17.0" secrets: build_env: ${{ secrets.DRIVE_BUILD_ENV }} diff --git a/.github/workflows/sdk-repin.yml b/.github/workflows/sdk-repin.yml index 085116b7..d27cc87d 100644 --- a/.github/workflows/sdk-repin.yml +++ b/.github/workflows/sdk-repin.yml @@ -31,6 +31,6 @@ jobs: with: sdk-version: ${{ github.event.client_payload.version || inputs.sdk-version || '' }} force: ${{ inputs.force == true }} - zig-version: "0.16.0" + zig-version: "0.17.0" secrets: build_env: ${{ secrets.DRIVE_BUILD_ENV }} diff --git a/build.zig b/build.zig index 531b7150..6455f829 100644 --- a/build.zig +++ b/build.zig @@ -27,7 +27,10 @@ pub fn build(b: *std.Build) void { /// strings when those are unset too, which builds a plugin that can only say "not configured". fn ensureCredentials(b: *std.Build) void { const io = b.graph.io; - if (b.build_root.handle.access(io, "credentials.zon", .{})) |_| return else |_| {} + // Declared either way, so a credentials.zon edited later reconfigures. The environment is + // read only to write a missing one: Zig 0.17 caches the configuration without regard to it. + defer b.dependOnFileContents(b.path("credentials.zon")); + if (b.root.access(io, "credentials.zon", .{})) |_| return else |_| {} const id = b.graph.environ_map.get("FIZZY_DRIVE_CLIENT_ID") orelse ""; const secret = b.graph.environ_map.get("FIZZY_DRIVE_CLIENT_SECRET") orelse ""; const web_id = b.graph.environ_map.get("FIZZY_DRIVE_WEB_CLIENT_ID") orelse ""; @@ -40,7 +43,8 @@ fn ensureCredentials(b: *std.Build) void { \\}} \\ , .{ id, secret, web_id }); - b.build_root.handle.writeFile(io, .{ .sub_path = "credentials.zon", .data = text }) catch |err| { + const path = b.root.joinString(b.allocator, "credentials.zon") catch @panic("OOM"); + std.Io.Dir.cwd().writeFile(io, .{ .sub_path = path, .data = text }) catch |err| { std.debug.panic("drive: could not write credentials.zon: {t}", .{err}); }; if (id.len == 0) std.debug.print("drive: no credentials.zon and FIZZY_DRIVE_CLIENT_ID unset — the plugin will refuse to sign in until one is provided\n", .{}); diff --git a/build.zig.zon b/build.zig.zon index 2aa02ea1..69a141fb 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,7 +1,7 @@ .{ .name = .drive, .version = "0.1.0", - .minimum_zig_version = "0.16.0", + .minimum_zig_version = "0.17.0", .paths = .{ "build.zig", "build.zig.zon", diff --git a/plugin.zig b/plugin.zig index 9e979214..c7733e60 100644 --- a/plugin.zig +++ b/plugin.zig @@ -191,7 +191,6 @@ pub fn register(host: *sdk.Host) !void { .run = nativeSignIn, }); } - } /// After the host injected its dvui globals: the transport (which keeps `dvui.io`), and the diff --git a/src/drive.zig b/src/drive.zig index 810532ef..3a1a4f82 100644 --- a/src/drive.zig +++ b/src/drive.zig @@ -1582,4 +1582,3 @@ test "rfc3339 to ms" { test { _ = @import("drive_test.zig"); } - diff --git a/src/drive_test.zig b/src/drive_test.zig index 50b8062c..789011c3 100644 --- a/src/drive_test.zig +++ b/src/drive_test.zig @@ -158,7 +158,7 @@ const notes_page = const little_drive = [_]Scripted.Route{ .{ .contains = "changes/startPageToken", .body = "{\"startPageToken\":\"100\"}" }, .{ .contains = "changes?", .body = - \\{"newStartPageToken":"101","changes":[{"fileId":"a1","removed":false,"file":{"id":"a1","name":"a.txt","parents":["n1"]}},{"fileId":"zz","removed":false,"file":{"id":"zz","name":"new.md","mimeType":"text/markdown","parents":["root"]}}]} + \\{"newStartPageToken":"101","changes":[{"fileId":"a1","removed":false,"file":{"id":"a1","name":"a.txt","parents":["n1"]}},{"fileId":"zz","removed":false,"file":{"id":"zz","name":"new.md","mimeType":"text/markdown","parents":["root"]}}]} }, .{ .contains = "pageToken=p2", .body = root_page2 }, .{ .contains = "q=%27root%27%20in%20parents", .body = root_page1 }, @@ -167,18 +167,18 @@ const little_drive = [_]Scripted.Route{ // The live metadata says someone saved a.txt a day after the listing. .{ .contains = "files/a1?fields=modifiedTime", .method = .GET, .body = "{\"modifiedTime\":\"2024-01-02T00:00:00Z\"}" }, .{ .contains = "files/d1?alt=media", .body = "never" }, - .{ .contains = "upload/drive/v3/files/a1?uploadType=media", .method = .PATCH, .body = - \\{"id":"a1","name":"a.txt","mimeType":"text/plain","size":"3","modifiedTime":"2024-06-01T00:00:00Z"} + .{ .contains = "upload/drive/v3/files/a1?uploadType=media", .method = .PATCH, .body = + \\{"id":"a1","name":"a.txt","mimeType":"text/plain","size":"3","modifiedTime":"2024-06-01T00:00:00Z"} }, .{ .contains = "files?fields=", .method = .POST, .body = - \\{"id":"new1","name":"b.txt","mimeType":"text/plain","size":"0"} + \\{"id":"new1","name":"b.txt","mimeType":"text/plain","size":"0"} }, - .{ .contains = "addParents=", .method = .PATCH, .body = - \\{"id":"a1","name":"renamed.txt","mimeType":"text/plain","size":"7"} + .{ .contains = "addParents=", .method = .PATCH, .body = + \\{"id":"a1","name":"renamed.txt","mimeType":"text/plain","size":"7"} }, .{ .contains = "?fields=id", .method = .PATCH, .body = "{\"id\":\"x\"}" }, .{ .contains = "files/a1?fields=", .method = .PATCH, .body = - \\{"id":"a1","name":"b.txt","mimeType":"text/plain","size":"7"} + \\{"id":"a1","name":"b.txt","mimeType":"text/plain","size":"7"} }, }; @@ -435,7 +435,7 @@ test "changes: the first poll takes a start token, the next folds changes into t test "duplicate sibling names: first listed wins" { const dup = [_]Scripted.Route{ .{ .contains = "in%20parents", .body = - \\{"files":[{"id":"x1","name":"same.txt","mimeType":"text/plain","size":"1"},{"id":"x2","name":"same.txt","mimeType":"text/plain","size":"2"}]} + \\{"files":[{"id":"x1","name":"same.txt","mimeType":"text/plain","size":"1"},{"id":"x2","name":"same.txt","mimeType":"text/plain","size":"2"}]} }, .{ .contains = "files/x1?alt=media", .body = "first" }, }; @@ -514,12 +514,12 @@ test "a re-list drops children that are gone and keeps a surviving folder's subt // same folder again, with `top.txt` back. const replaced = [_]Scripted.Route{ .{ .contains = "q=%27root%27%20in%20parents", .body = - \\{"files":[{"id":"n2","name":"notes","mimeType":"application/vnd.google-apps.folder"}]} + \\{"files":[{"id":"n2","name":"notes","mimeType":"application/vnd.google-apps.folder"}]} }, }; const same = [_]Scripted.Route{ .{ .contains = "q=%27root%27%20in%20parents", .body = - \\{"files":[{"id":"n1","name":"notes","mimeType":"application/vnd.google-apps.folder","modifiedTime":"2024-03-01T00:00:00Z"},{"id":"t1","name":"top.txt","mimeType":"text/plain","size":"9"}]} + \\{"files":[{"id":"n1","name":"notes","mimeType":"application/vnd.google-apps.folder","modifiedTime":"2024-03-01T00:00:00Z"},{"id":"t1","name":"top.txt","mimeType":"text/plain","size":"9"}]} }, }; @@ -584,8 +584,8 @@ test "a spent quota is retried, not reported as a refusal" { test "a refusal that is not a quota is still a refusal" { const a = std.testing.allocator; const h = try Harness.init(a, &.{ - .{ .contains = "files?q=", .status = 403, .body = - \\{"error":{"code":403,"message":"Insufficient permission","errors":[{"reason":"insufficientPermissions"}]}} + .{ .contains = "files?q=", .status = 403, .body = + \\{"error":{"code":403,"message":"Insufficient permission","errors":[{"reason":"insufficientPermissions"}]}} }, }); defer h.deinit(); @@ -613,8 +613,8 @@ test "a folder that came back as a file forgets what was beneath it" { // Same id `n1`, same name, now a plain file. const notes_is_a_file = [_]Scripted.Route{ .{ .contains = "pageToken=p2", .body = root_page2 }, - .{ .contains = "q=%27root%27%20in%20parents", .body = - \\{"nextPageToken":"p2","files":[{"id":"n1","name":"notes","mimeType":"text/plain","size":"3"}]} + .{ .contains = "q=%27root%27%20in%20parents", .body = + \\{"nextPageToken":"p2","files":[{"id":"n1","name":"notes","mimeType":"text/plain","size":"3"}]} }, }; h.scripted.routes = ¬es_is_a_file; @@ -638,24 +638,24 @@ test "a folder that came back as a file forgets what was beneath it" { // accepted in a query and never reported back. const walk_drive = [_]Scripted.Route{ .{ .contains = "q=%28%27root%27%20in%20parents%29", .body = - \\{"files":[{"id":"n1","name":"notes","mimeType":"application/vnd.google-apps.folder","parents":["0Aroot"]},{"id":"m1","name":"more","mimeType":"application/vnd.google-apps.folder","parents":["0Aroot"]},{"id":"t1","name":"top.md","mimeType":"text/markdown","size":"3","parents":["0Aroot"]}]} + \\{"files":[{"id":"n1","name":"notes","mimeType":"application/vnd.google-apps.folder","parents":["0Aroot"]},{"id":"m1","name":"more","mimeType":"application/vnd.google-apps.folder","parents":["0Aroot"]},{"id":"t1","name":"top.md","mimeType":"text/markdown","size":"3","parents":["0Aroot"]}]} }, // Both of root's folders in one query, answered in one page. .{ .contains = "q=%28%27n1%27%20in%20parents%20or%20%27m1%27%20in%20parents%29", .body = - \\{"files":[{"id":"a1","name":"a.md","mimeType":"text/markdown","size":"5","parents":["n1"]},{"id":"s1","name":"sub","mimeType":"application/vnd.google-apps.folder","parents":["n1"]},{"id":"b1","name":"b.md","mimeType":"text/markdown","size":"6","parents":["m1"]}]} + \\{"files":[{"id":"a1","name":"a.md","mimeType":"text/markdown","size":"5","parents":["n1"]},{"id":"s1","name":"sub","mimeType":"application/vnd.google-apps.folder","parents":["n1"]},{"id":"b1","name":"b.md","mimeType":"text/markdown","size":"6","parents":["m1"]}]} }, .{ .contains = "q=%28%27s1%27%20in%20parents%29", .body = - \\{"files":[{"id":"d1","name":"deep.md","mimeType":"text/markdown","size":"7","parents":["s1"]}]} + \\{"files":[{"id":"d1","name":"deep.md","mimeType":"text/markdown","size":"7","parents":["s1"]}]} }, // One folder on its own — a listing that did not wait for the walk. .{ .contains = "q=%27root%27%20in%20parents", .body = - \\{"files":[{"id":"n1","name":"notes","mimeType":"application/vnd.google-apps.folder"},{"id":"m1","name":"more","mimeType":"application/vnd.google-apps.folder"},{"id":"t1","name":"top.md","mimeType":"text/markdown","size":"3"}]} + \\{"files":[{"id":"n1","name":"notes","mimeType":"application/vnd.google-apps.folder"},{"id":"m1","name":"more","mimeType":"application/vnd.google-apps.folder"},{"id":"t1","name":"top.md","mimeType":"text/markdown","size":"3"}]} }, .{ .contains = "q=%27n1%27%20in%20parents", .body = - \\{"files":[{"id":"a1","name":"a.md","mimeType":"text/markdown","size":"5"},{"id":"s1","name":"sub","mimeType":"application/vnd.google-apps.folder"}]} + \\{"files":[{"id":"a1","name":"a.md","mimeType":"text/markdown","size":"5"},{"id":"s1","name":"sub","mimeType":"application/vnd.google-apps.folder"}]} }, .{ .contains = "q=%27s1%27%20in%20parents", .body = - \\{"files":[{"id":"d1","name":"deep.md","mimeType":"text/markdown","size":"7"}]} + \\{"files":[{"id":"d1","name":"deep.md","mimeType":"text/markdown","size":"7"}]} }, }; @@ -788,10 +788,10 @@ test "a refused batch is asked again in halves" { // The two-folder query is "too complex"; each folder alone is fine. .{ .contains = "q=%28%27n1%27%20in%20parents%20or", .status = 400, .body = "{\"error\":{\"message\":\"The query is too complex.\"}}" }, .{ .contains = "q=%28%27n1%27%20in%20parents%29", .body = - \\{"files":[{"id":"a1","name":"a.md","mimeType":"text/markdown","parents":["n1"]}]} + \\{"files":[{"id":"a1","name":"a.md","mimeType":"text/markdown","parents":["n1"]}]} }, .{ .contains = "q=%28%27m1%27%20in%20parents%29", .body = - \\{"files":[{"id":"b1","name":"b.md","mimeType":"text/markdown","parents":["m1"]}]} + \\{"files":[{"id":"b1","name":"b.md","mimeType":"text/markdown","parents":["m1"]}]} }, }; const h = try Harness.init(std.testing.allocator, &routes); From 539f3c311d1d6a34a88b6909f69aaf6e26387af4 Mon Sep 17 00:00:00 2001 From: foxnne Date: Sun, 18 Oct 2026 17:50:31 -0500 Subject: [PATCH 2/2] sdk: repin to fizzy SDK 0.2.30 The first SDK on Zig 0.17. Co-Authored-By: Claude Opus 5.5 --- build.zig.zon | 4 ++-- plugin.zig.zon | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 7bfa93eb..6a36d55a 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -26,8 +26,8 @@ // The published SDK, not a path into a checkout: a release has to build from a // clean clone, and the release asset (never the tag's git archive) is the SDK // package alone — the monorepo root zon carries the app's own dependencies. - .url = "https://github.com/fizzyedit/fizzy/releases/download/sdk-v0.2.29/fizzy-sdk-v0.2.29.tar.gz", - .hash = "fizzy-0.2.29-SGBwY2sxJgCLLSPt4HLdlMSg7WtcP4K0fdtXI5WwJl2d", + .url = "https://github.com/fizzyedit/fizzy/releases/download/sdk-v0.2.30/fizzy-sdk-v0.2.30.tar.gz", + .hash = "fizzy-0.2.30-SGBwY1QnJwDznUMVnSLATxXgH2e8_Vz7eMwLj9UQ0sVU", }, }, } diff --git a/plugin.zig.zon b/plugin.zig.zon index 12bfbb18..bdcd09a0 100644 --- a/plugin.zig.zon +++ b/plugin.zig.zon @@ -1,8 +1,8 @@ .{ .id = "drive", .name = "Google Drive", - .version = "0.1.17", - .min_sdk_version = "0.2.29", + .version = "0.1.18", + .min_sdk_version = "0.2.30", .description = "Sign in to Google Drive and browse, open and edit it in the explorer like a folder", .tags = .{ "cloud", "drive", "mount" }, .author = "foxnne",