Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package-lock.json
zig-out
zig-cache
.zig-cache
zig-pkg
.direnv
PDF
.DS_Store
Expand Down
173 changes: 93 additions & 80 deletions build/0.16.zig
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
const std = @import("std");
const Build = std.Build;
const ChildProcess = std.process.Child;

const log = std.log.scoped(.For_0_16_0);
const version = "16";

Expand All @@ -13,99 +11,114 @@ pub fn build(b: *Build) void {
// get target and optimize
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const io = b.graph.io;

var lazy_path = b.path(relative_path);

const full_path = lazy_path.getPath(b);

// open dir
var dir = std.fs.openDirAbsolute(full_path, .{ .iterate = true }) catch |err| {
log.err("open 15 path failed, err is {}", .{err});
var dir = std.Io.Dir.openDirAbsolute(io, full_path, .{ .iterate = true }) catch |err| {
log.err("open 16 path failed, err is {}", .{err});
std.process.exit(1);
};
defer dir.close();
defer dir.close(io);

// make a iterate for release ath
var iterate = dir.iterate();

while (iterate.next()) |val| {
if (val) |entry| {
// get the entry name, entry can be file or directory
const output_name = std.mem.trimRight(u8, entry.name, ".zig");
if (entry.kind == .file) {
// connect path
const path = std.fs.path.join(b.allocator, &[_][]const u8{ relative_path, entry.name }) catch |err| {
log.err("fmt path for examples failed, err is {}", .{err});
std.process.exit(1);
};

// build exe
const exe = b.addExecutable(.{
.name = output_name,
.root_module = b.addModule(output_name, .{
.root_source_file = b.path(path),
.target = target,
.optimize = optimize,
}),
});
exe.linkLibC();

if (exe.root_module.resolved_target.?.result.os.tag == .windows and std.mem.eql(u8, "echo_tcp_server.zig", entry.name)) {
std.log.info("link ws2_32 for {s}", .{entry.name});
exe.linkSystemLibrary("ws2_32");
}
// add to default install
b.installArtifact(exe);

// build test
const test_name = std.fmt.allocPrint(b.allocator, "{s}_test", .{output_name}) catch |err| {
log.err("fmt test name failed, err is {}", .{err});
std.process.exit(1);
};
const unit_tests = b.addTest(.{
.root_module = b.addModule(test_name, .{
.root_source_file = b.path(path),
.target = target,
.optimize = optimize,
}),
while (iterate.next(io) catch |err| {
log.err("iterate examples_path failed, err is {}", .{err});
std.process.exit(1);
}) |entry| {
// get the entry name, entry can be file or directory
const output_name = if (std.mem.endsWith(u8, entry.name, ".zig"))
entry.name[0 .. entry.name.len - ".zig".len]
else
entry.name;
if (entry.kind == .file) {
// connect path
const path = std.fs.path.join(b.allocator, &[_][]const u8{ relative_path, entry.name }) catch |err| {
log.err("fmt path for examples failed, err is {}", .{err});
std.process.exit(1);
};

const imports: []const std.Build.Module.Import = if (std.mem.eql(u8, entry.name, "interact_with_c.zig")) imports: {
const c_header = b.addWriteFiles().add("interact_with_c.h",
\\#define _NO_CRT_STDIO_INLINE 1
\\#include <stdio.h>
);
const translate_c = b.addTranslateC(.{
.root_source_file = c_header,
.target = target,
.optimize = optimize,
});

// add to default install
b.getInstallStep().dependOn(&b.addRunArtifact(unit_tests).step);
} else if (entry.kind == .directory) {

// build child process
var child = ChildProcess.init(&args, b.allocator);

// build cwd
const cwd = std.fs.path.join(b.allocator, &[_][]const u8{
full_path,
entry.name,
}) catch |err| {
log.err("fmt path for examples failed, err is {}", .{err});
std.process.exit(1);
};

// open entry dir
const entry_dir = std.fs.openDirAbsolute(cwd, .{}) catch unreachable;
entry_dir.access("build.zig", .{}) catch {
log.err("not found build.zig in path {s}", .{cwd});
std.process.exit(1);
break :imports &[_]std.Build.Module.Import{
.{ .name = "c", .module = translate_c.createModule() },
};

// set child cwd
// this api maybe changed in the future
child.cwd = cwd;

// spawn and wait child process
_ = child.spawnAndWait() catch unreachable;
} else &.{};

// build exe
const exe = b.addExecutable(.{
.name = output_name,
.root_module = b.addModule(output_name, .{
.root_source_file = b.path(path),
.target = target,
.optimize = optimize,
.imports = imports,
}),
});
exe.root_module.linkSystemLibrary("c", .{});

if (exe.root_module.resolved_target.?.result.os.tag == .windows and std.mem.eql(u8, "echo_tcp_server.zig", entry.name)) {
std.log.info("link ws2_32 for {s}", .{entry.name});
exe.root_module.linkSystemLibrary("ws2_32", .{});
}
} else {
// Stop endless loop
break;
// add to default install
b.installArtifact(exe);

// build test
const test_name = std.fmt.allocPrint(b.allocator, "{s}_test", .{output_name}) catch |err| {
log.err("fmt test name failed, err is {}", .{err});
std.process.exit(1);
};
const unit_tests = b.addTest(.{
.root_module = b.addModule(test_name, .{
.root_source_file = b.path(path),
.target = target,
.optimize = optimize,
.imports = imports,
}),
});

// add to default install
b.getInstallStep().dependOn(&b.addRunArtifact(unit_tests).step);
} else if (entry.kind == .directory) {

// build child process
// build cwd
const cwd = std.fs.path.join(b.allocator, &[_][]const u8{
full_path,
entry.name,
}) catch |err| {
log.err("fmt path for examples failed, err is {}", .{err});
std.process.exit(1);
};

// open entry dir
const entry_dir = std.Io.Dir.openDirAbsolute(io, cwd, .{}) catch unreachable;
defer entry_dir.close(io);

entry_dir.access(io, "build.zig", .{}) catch {
log.err("not found build.zig in path {s}", .{cwd});
std.process.exit(1);
};

var child = std.process.spawn(io, .{
.argv = &args,
.cwd = .{ .path = cwd },
}) catch unreachable;
_ = child.wait(io) catch unreachable;
}
} else |err| {
log.err("iterate examples_path failed, err is {}", .{err});
std.process.exit(1);
}
}
8 changes: 8 additions & 0 deletions course/.vitepress/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,14 @@ export default [
text: "版本说明",
collapsed: true,
items: [
{
text: "0.16.0 升级指南",
link: "/update/upgrade-0.16.0",
},
{
text: "0.16.0 版本说明",
link: "/update/0.16.0-description",
},
{
text: "0.15.1 升级指南",
link: "/update/upgrade-0.15.1",
Expand Down
2 changes: 1 addition & 1 deletion course/.vitepress/theme/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
const version: string = "0.15.1";
const version: string = "0.16.0";

export { version };
4 changes: 2 additions & 2 deletions course/advanced/assembly.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ outline: deep

<<<@/code/release/assembly.zig#inline_assembly

上面这段代码是通过内联汇编实现在 x86-64 linux 下输出 `hello world`,接下来讲解一下它们的组成和使用
上面这段示例当前没有直接执行内联汇编:旧的 `syscall` 写法保留在注释中,实际路径只调用 `std.process.exit(0)`,避免把未迁移的内联汇编语法误当作 Zig 0.16 可运行示例

内联汇编是以 `asm` 关键字开头的一个表达式,这说明它可以返回值(也可以不返回值),`volatile` 关键字会通知编译器,内联汇编的表达式会被某些编译器未知的因素更改(例如操作系统,硬件 MMIO 或者其他线程等等),这样编译器就不会额外优化这段内联汇编。

上面就是基本的内联汇编的一个外部结构说明,接下来我们介绍具体的内部结构
下面是内联汇编表达式的一般结构

```zig
asm volatile ("assembly code"
Expand Down
10 changes: 5 additions & 5 deletions course/advanced/atomic.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ outline: deep
目前 Zig 提供了一些内建函数来进行原子操作,并且提供了 `std.atomic` 命名空间来实现内存排序、原子数据结构。

> [!TIP]
> 该部分内容更适合在单片机或者某些系统级组件开发上使用,常规使用可以使用 `std.Thread` 命名空间下的类型,包含常规的 `Mutex``Condition``ResetEvent`,`WaitGroup`等等
> 该部分内容更适合在单片机或者某些系统级组件开发上使用,常规使用可以使用 `std.Io` 命名空间下的同步原语,例如 `Mutex``Condition``ResetEvent`,以及 `std.Io.Group` / `std.Io.async` 这类任务同步接口

## 内建函数

Expand Down Expand Up @@ -122,7 +122,7 @@ outline: deep
- **`ref()` 方法**:使用 `.monotonic` 顺序递增计数器。由于只是简单地增加计数,不需要与其他内存操作建立同步关系。
- **`unref()` 方法**:使用 `.release` 顺序递减计数器,确保在计数递减之前的所有内存操作对其他线程可见。当计数减到 1 时,使用 `.acquire` 加载来获取之前所有 `unref()` 操作形成的 release 序列,确保可以安全地调用清理函数。

这种模式常用于智能指针、共享资源管理等场景,是替代 `Mutex` 的轻量级线程安全方案
这种模式常用于智能指针、共享资源管理等场景,是替代 `std.Io.Mutex` 的轻量级线程安全方案

### `spinLoopHint` 自旋锁

Expand All @@ -142,11 +142,11 @@ outline: deep
- 当等待某个原子变量的值发生变化时
- 当预期等待时间非常短(纳秒到微秒级别)时

**与 Mutex 的选择**:
**与 `std.Io.Mutex` 的选择**:

- **自旋等待**:适合极短时间的等待,避免线程上下文切换的开销
- **Mutex**:适合等待时间不确定或较长的场景,会让出 CPU 给其他线程
- **`std.Io.Mutex`**:适合等待时间不确定或较长的场景,会让出 CPU 给其他线程

::: warning ⚠️ 警告
不当使用自旋等待会导致 CPU 资源浪费。如果等待时间较长或不确定,应使用 `std.Thread.Mutex` 等同步原语
不当使用自旋等待会导致 CPU 资源浪费。如果等待时间较长或不确定,应使用 `std.Io.Mutex` 这类会让出 CPU 的同步原语
:::
63 changes: 39 additions & 24 deletions course/advanced/interact-with-c.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,30 +36,52 @@ Zig 定义了几个对应 C ABI 的基本类型:

## C Header 导入

C 语言共享类型通常是通过引入头文件实现,这点在 zig 中可以无缝做到,得益于 zig 的 **translate-c** 功能
C 语言共享类型通常通过引入头文件实现。Zig 0.16 起推荐把头文件翻译放到 `build.zig` 中:先用 `addTranslateC` 生成模块,再在 Zig 代码里像普通模块一样 `@import("c")`

接下来展示一个例子,简单地引入 c 标准库的 `printf` 函数
`build.zig` 中的核心写法如下

<<<@/code/release/interact_with_c.zig#cHeaderImport
```zig
const translate_c = b.addTranslateC(.{
.root_source_file = b.path("src/c.h"),
.target = target,
.optimize = optimize,
});

const exe = b.addExecutable(.{
.name = "example",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "c", .module = translate_c.createModule() },
},
}),
});
exe.root_module.linkSystemLibrary("c", .{});
```

::: info 🅿️ 提示
`src/c.h` 中放需要翻译的 C 头文件:

注意:为了构建这个,我们需要引入 `libc`,可以在 `build.zig` 中添加 `exe.linkLibC` 函数,`exe` 是默认的构建变量。
```c
#define _NO_CRT_STDIO_INLINE 1
#include <stdio.h>
```

或者我们可以手动执行构建:`zig build-exe source.zig -lc`
接下来展示 Zig 调用侧的例子,简单地引入 C 标准库的 `printf` 函数:

:::

[`@cImport`](https://ziglang.org/documentation/master/#cImport) 函数接受一个表达式作为参数,该表达式会在编译期执行,用于控制预处理器指令并引入头文件。
<<<@/code/release/interact_with_c.zig#cHeaderImport

::: info 🅿️ 提示

表达式内应仅包含 [`@cInclude`](https://ziglang.org/documentation/master/#cInclude)、[`@cDefine`](https://ziglang.org/documentation/master/#cDefine)、[`@cUndef`](https://ziglang.org/documentation/master/#cUndef),它们会在编译时进行解析并转换为 C 代码
注意:为了构建这个,我们需要引入 `libc`。在 Zig 0.16 的构建脚本中,可以让对应模块链接 C 标准库,例如 `exe.root_module.linkSystemLibrary("c", .{})`

通常情况下,应当只存在一个 `@cImport`,这是防止编译器重复调用 clang,并且避免内联函数被重复,只有为了避免符号冲突(两个文件均定义了相同的标识符)和分析具有不同预处理定义的代码时才出现多个 `@cImport`。
因此通常通过 `zig build` 驱动这个例子;旧的 `@cImport` 单文件代码才适合手动 `zig build-exe source.zig -lc`。

:::

`@cImport` 仍然保留,但在 Zig 0.16 已进入 deprecated 迁移期;它只适合维护旧的单文件示例或历史代码。新代码不要再把它作为 C 头文件入口,应优先使用上面的 `build.zig` + `@import("c")` 路径。

## vcpkg C Lib 导入

> [!IMPORTANT]
Expand All @@ -75,7 +97,7 @@ C 语言共享类型通常是通过引入头文件实现,这点在 zig 中可

<<<@/code/release/import_vcpkg/build.zig#c_import

假设你想要借用 `gsl` 库来对数值进行傅里叶变换,那么可以先导入
假设你想要借用 `gsl` 库来对数值进行傅里叶变换,Zig 0.16 新代码应沿用上面的 `addTranslateC` + `@import("c")` 路径。下面这个历史片段仍使用旧的 `@cImport`,仅用于说明要导入的 GSL 头文件,不作为新项目推荐写法:

<<<@/code/release/import_vcpkg/src/main.zig#import_gsl

Expand Down Expand Up @@ -108,26 +130,19 @@ Zig 提供了一个命令行工具 `zig translate-c` 供我们使用,它可以

:::

### `@cImport` vs `translate-c`
### 构建系统 `translate-c` 与命令行 `translate-c`

事实上,这两者底层实现是一样的,`@cImport` 一般用于使用 C 库时引入头文件,而 `translate-c` 通常是为了修改翻译后的代码,例如:将 `anytype` 修改为更加精确的类型、将 `[*c]T` 指针修改为 `[*]T` 或者 `*T` 来提高类型安全性、启动或者禁用某些运行时的安全性功能。
构建系统里的 `addTranslateC` 是 Zig 0.16 推荐的头文件导入路径;命令行 `zig translate-c` 更适合一次性查看或手动修改翻译后的代码,例如:将 `anytype` 修改为更加精确的类型、将 `[*c]T` 指针修改为 `[*]T` 或者 `*T` 来提高类型安全性、启动或者禁用某些运行时的安全性功能。

## C 翻译缓存

C 翻译功能(无论是通过 `zig translate-c` 还是 `@cImport` 使用)与 Zig 缓存系统集成。使用相同源文件、目标和 `cflags` 的后续构建将使用缓存,而不是重复翻译相同的代码。
C 翻译功能(通过 `build.zig` 的 `addTranslateC` 或 `zig translate-c` 使用)与 Zig 缓存系统集成。使用相同源文件、目标和 `cflags` 的后续构建将使用缓存,而不是重复翻译相同的代码;构建缓存目录是项目下的 `.zig-cache`

要在编译使用 `@cImport` 引入的代码时打印缓存文件的存储位置,请使用 `--verbose-cimport` 参数
下面这个 Zig 文件片段是 `addTranslateC` 生成模块后的调用侧

<<<@/code/release/interact_with_c.zig#cTranslate

```sh
$ zig build-exe test.zig -lc --verbose-cimport
info(compilation): C import source: /home/username/.cache/zig/o/6f35761b17b87ee4c9f26e643a06e289/cimport.h
info(compilation): C import .d file: /home/username/.cache/zig/o/6f35761b17b87ee4c9f26e643a06e289/cimport.h.d
info(compilation): C import output: /home/username/.cache/zig/o/86899cd499e4c3f94aa141e400ac265f/cimport.zig
```

`cimport.h` 包含要翻译的文件(通过调用 `@cInclude`、`@cDefine` 和 `@cUndef` 构建),`cimport.h.d` 是文件依赖项列表,`cimport.zig` 包含翻译后的代码。
如果你还在维护旧的 `@cImport` 代码,`--verbose-cimport` 可以临时用于查看旧导入缓存位置,便于迁移或排查。

## C 翻译错误

Expand Down
Loading
Loading