Skip to content

proposal: 包管理 #10

@weiwenhao

Description

@weiwenhao

import 不会影响已经确定的全局符号的名称,只是提供一种能够找到 module 的方式。module 依旧是代码组织的基本模块。

将会进一步利用文件与文件夹名称阐述更多的信息,而不需要类似 package, mod 这样的声明。

sub module

如何将单个 module 分散到多个文件?

  1. user_basic.n
  2. user.basic.n

golang 在面对跨平台代码编译时采用了第一种方式,但是下划线是常用的分词方式,所以无法区分 user_basic.n 是 module 划分还是用户自定义的名称的划分。因此尝试使用第二种方式。

user.n 和 user.[sub].n 都会被视为 user module,被编译到一个文件中。

但是当使用 import file 的方式引入 module 时,并不能解锁 sub module 的功能。

import "user.n" 仅会触发对 user.n 的 import 而不包含 user.[sub].n

并且通过 import file 的方式,由于 path 仅支持以当前文件作为根路径向下寻找 module, 所以无法支持如

├── main.n
├── post
│   └── post.n
├── user
│   └── user.n
└── utils
    └── foo.n

这种情况下 main.n 可以通过 import "post/post.n" 或者 import "utils/foo.n" 引入 post 和 foo 模块,但是在 post.n 中相邻的 user 和 utils 模块是不可见的。除非能够支持 import "../utils/foo.n" ,不过并没有计划支持这样的 import file 方式。所以接下来必须借助包管理工具实现了。

package

在项目的根目录下需要有一个配置文件,暂定 package.toml

# 基础信息
name = "hello"
version = "0.1.0"
authors = ["Alice <a@example.com>", "Bob <b@example.com>"]
description = "Your package description"
license = "MIT"
type = "lib" # lib or bin
path = "hello.n" # 自定义入口文件

[dependencies]
rand = {  type = "git", version = "1.0", url = "https://github.com/foo/rand" }
seed = {  type = "local", version = "1.1.0", path = "~/Code/custom.1" }
  • version 使用的是精确的版本,尤其是在 git 时,其作为 branch/tag 进行包的拉取。
  • url 中的名字可以与包名不同,甚至完全不包含与包名相关的符号。
  • 原则上 dependencies key 需要和 package.name 相同,但是考虑到存在两个同名的包做不同的功能,所以 dependencies key 充当了 alias 的功能。当然,默认情况下推荐使用相同的名称。

当前版本暂时不去支持约束范围,仅需要支持精确版本。

package.toml 就放在项目的根目录下,执行 nature build 时如何能够找到 package.json 呢?

严格要求执行 shell 所在的目录,也就是工作目录必须存在 package.toml 所在目录。

既然已经有了这个基础限制,那么即使没有开启 package 时也应该遵循这个规范。但是不做强制检测。如 nature build src/main.n 是被允许的,这样可以在一个项目中构建多个二进制文件。在没有 package.toml 的情况下,main.n 所在到目录将会被识别为工作目录。

没有 package.toml 时,虽然 nature build 会继续执行编译命令,但是会退化成基于 import file 的模式,在非 package 模式下,一旦引用层级中出现了 import package 的方式,应该立即抛出异常。

where to put?

对于比如 url = http://test.com/foo/rand golang 中进行多级的拆分,且文件夹中包含了 @ 符号,从而可以判断出我是否加载过这个 package。 nature 则直接将 / 转换成 . 并携带上版本符号,而 local 则不需要 copy 到 sources 文件夹,每一次都去 local path 进行 import 即可,示例:

.nature
└── package
    ├── caches
    │   └── github.com.foo.rand@1.0
    │       ├── rand@1.0.other.n.o
    │       ├── rand@1.0.rand.n.o
    │       ├── rand@1.0.utils.pool.n.o
    │       └── rand@1.0.utils.seed.n.o
    └── sources
        └── github.com.foo.rand@1.0
            ├── other.n
            ├── package.toml # name = rand
            ├── rand.n
            ├── rand.sub.n
            └── utils
                ├── pool.n
                ├── seed.n
                └── seed.sub.n

caches 机制留到下一个版本再去实现。

import file

语法依旧为 import "test.n" 或者 import "foo/test.n"

因为一个 file 可以被多个其他 file import, 所以 file 自身应该具有一个唯一前置标识。

上一个版本中 import file 存在 bug,是关于如何确定 import file 编译时符号唯一名称的 bug,当工作目录不在入口文件目录时会触发 import 异常。

当启用了 package.toml 时,应该以 package.toml 中的 name + version 作为入口。

当没有启用 package.toml 时,如 nature build main.n,main.n 具有入口的属性,且 main.n 只能 import 低于自身等级的 module。 所以可以用 main.n 所在目录作为入口。但是确定了入口又有什么用呢?

假设入口 = hello,首先 import 是一种寻找 module 的方式,不会影响 module 的唯一标识,通过 import 可以定位到 module 的 full path。具体的示例如下

without package.toml

test.n = /home/weiwenhao/code/nature-test/foo/test.n
main.n = /home/weiwenhao/code/nature-test/main.n

则入口为 main. 所在目录,即 nature-test,可以将 test.n 相对截取出 foo/test.n ,去掉 n 并将斜杠替换成 ., 再将入口添加进去可以得到一个全局唯一符号 nature-test.foo.test,该符号将作为一个前缀,所有的子符号都会携带上该前缀。

compiler 并不会对项目中的所有 module 都进行编译,仅当 import 时才会触发编译。

如果同时有 import "test.n"import "foo/test.n" 时,如何避免重复编译并且得到这个 module 的 unique_ident,进行引用改写呢? 可以使用 hash table 存储编译数据,使用 full path 作为 key。这样只要能够得到 full path 就行了。

exists package.toml

适用于 lib/bin 类型的 package, 在 lib package 中,同样允许使用 import path 的方式引入 module。

test.n = /home/weiwenhao/code/nature-test/foo/test.n
main.n = /home/weiwenhao/code/nature-test/main.n
package.toml = /home/weiwenhao/code/nature-test/package.toml
name=test
version=0.1.0

此时 package 的入口应该是携带上版本的 nature-test@0.1.0

main.n 中包含 import "foo/test.n" 通过 import 可以找到 test.n 的 full path,进行 module 的编译,此时的 unique ident 应该为 test@0.1.0.foo.test


所以 package 的入口关系到了 module 的 unique ident 的生成。后续的 import package 将会是一种崭新的 import 方式,但是再次重复, import 只是一种寻找包的 full path 方式,并不会影响 module unique ident 的生成。

import package

即使使用上面的示例

github.com.foo.rand@1.0
├── package.toml
├── rand.n
├── rand.sub.n
├── other.n
└── utils
    ├── pool.n
    ├── seed.sub.n
    └── seed.n

package.toml 中 name = rand。

rand.n 文件的内容大致如下

import "utils/seed.n"
// 这里的 rand 是 package.name,其能够作为起点,找到当前 package 下的所有 module
import rand.utils.pool
import rand.other

fn interger():int {
}

现在如果有一个 bin 类型的项目,通过 package.toml 安装依赖 1.0 版本的 rand, 也就是 package 处给的示例。那在 bin 的入口 main.n 中如何使用这个 package 呢? 示例

import rand // 这里 import 了 rand package,并且默认导向 rand.n module, 其相当于 rand.rand

var i = rand.interger() // v 通过这种方式可以调用 rand module 中的全局函数/变量

var b = rand.utils.seed.set() // x 不能通过这种方式引入 package 中的其他 module

import package 时不需要再使用双引号啦,且使用点语法进行 module 的引入,从而和 import file 进行区分。如果想要 import rand package 中的其他 module 则可以

import rand.other // v
import rand.rand // v
import rand.utils.seed // v, 这里包含 seed.n 和 seed.sub.n 构成的一个 seed module
import rand.utils.pool // v 

compiler

上面已经约定好了 module unique ident, 在使用时,比如 main.n 包含如下内容

import rand.utils.seed

var b = seed.set()

在检测到 seed 存在于 import 中,应该按照改写约定,将 seed.set 改写成全局唯一符号。也就是 rand@1.0.utils.seed.set 虽然有很多的点,但是要记住,这是一个字符串类型的符号。

这里改写的名称应该是在全局符号表中能够找到的名称,存在一种可能,比如 dependencies key 是 foo,所以 import 是 foo, 但是实际上 package.name 是 bar, 此时应该要识别出这种情况,改写时应该改写成 package.name = bar,因为其在全局符号表中构建的 ident 为 bar

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions