Skip to content

Commit

Permalink
Merge pull request #37 from xen0n/blob-and-external-distfiles
Browse files Browse the repository at this point in the history
Blob packages and external distfiles
  • Loading branch information
xen0n authored Jan 15, 2024
2 parents de58524 + 1e64481 commit 0061c08
Show file tree
Hide file tree
Showing 7 changed files with 294 additions and 77 deletions.
14 changes: 13 additions & 1 deletion docs/repo-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ packages-index
* `slug` 是可选的便于称呼该包的全局唯一标识。目前未有任何特定的命名规范,待后续出现第三方软件源再行定义。
* `kind` 说明软件包的性质。目前定义了以下几种:
- `binary`:该包为二进制包,安装方式为直接解压。
- `blob`:该包为不需安装动作、非结构化的纯二进制数据。
- `source`:该包为源码包,安装方式为直接解压。
- `toolchain`:该包提供了一套工具链。
- `emulator`:该包提供了一个或多个模拟器二进制。
Expand All @@ -165,7 +166,11 @@ packages-index
- `name`:提供者名称,目前仅用于向用户展示。
- `eula`:目前仅支持取值为 `null`,表示安装该包前不需要征得用户明确同意任何协议。
* `distfiles` 内含包的相关分发文件(distfile)声明。其中每条记录:
- `name` 是文件名,表示此文件可从 `${config.dist}/dist/${name}` 这样的路径获取到。
- `name` 是文件名。当 `urls` 字段不存在时,表示此文件可从 `${config.dist}/dist/${name}` 这样的路径获取到。
- `urls` 是可选的 URL 字符串列表,表示此文件可额外从这些 URL 中的任意一个获取到。下载到本地的文件仍应被保存为 `name` 所指的文件名。
- `restrict` 是可选的对于该文件应施加的额外限制列表。每个元素可选以下之一:
- `mirror`:该文件只能从 `urls` 所给定的 URLs 获取,不要试图从镜像源获取(默认会带上对应从镜像源获取的 URL,且优先从镜像源获取)。
- `fetch`:该文件不应被自动获取。应提示用户自行下载并放置于规定位置,尔后再重试其先前操作。
- `size` 是以字节计的文件大小,用于完整性校验。
- `checksums` 是文件内容校验和的 K-V 映射,每条记录的 key 为所用的算法,value 为按照该算法得到的该文件预期的校验和。目前接受以下几种算法:
- `sha256`:值为文件 SHA256 校验和的十六进制表示。
Expand All @@ -174,6 +179,8 @@ packages-index
* `binary` 仅在 `kind` 含有 `binary` 时有意义,表示适用于二进制包的额外信息。其类型为列表,每条记录:
- `host` 代表该条记录所指的二进制包适用的宿主架构。宿主架构的语义与 Python 的 `platform.machine()` 返回值相同。
- `distfiles` 是分发文件名的列表,每条分发文件的具体定义参照 `distfiles` 字段。要为此宿主架构安装该包,下载并解压所有这些分发文件到相同目标目录即可。
* `blob` 仅在 `kind` 含有 `blob` 时有意义,表示适用于二进制数据包的额外信息。其中:
- `distfiles` 是分发文件名的列表,每条分发文件的具体定义参照 `distfiles` 字段。此包不应被安装;对分发文件的引用应直接指向相应文件的下载目的地。
* `source` 仅在 `kind` 含有 `source` 时有意义,表示适用于源码包的额外信息。其中:
- `distfiles` 是分发文件名的列表,每条分发文件的具体定义参照 `distfiles` 字段。要向某目标目录解压该源码包,下载并解压所有这些分发文件到该目标目录即可。
* `toolchain` 仅在 `kind` 含有 `toolchain` 时有意义,表示适用于工具链包的额外信息。
Expand All @@ -193,6 +200,11 @@ packages-index
- `binfmt_misc` 是适合该二进制的 Linux `binfmt_misc` 配置串。注意转义。其中支持的特殊写法:
- `$BIN`:将在渲染时被替换为指向该二进制的绝对路径。

同时请注意,目前 `ruyi` 的参考实现存在如下的特殊情况:

* 目前未实现分发文件的 `restrict: fetch` 功能。其具体实现细节仍待进一步细化。
* 目前 Zip 压缩包的解压工作由系统的 `unzip` 命令提供。由于该命令不支持类似 `tar``--strip-components` 选项,因此 Zip 格式的分发文件的 `strip_components` 配置目前不会被尊重。

### `news`

此目录内含 0 或多份 Markdown 格式的通知消息。
Expand Down
4 changes: 4 additions & 0 deletions ruyi/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ def global_binary_install_root(self, host: str, slug: str) -> str:
path = pathlib.Path(self.ensure_data_dir()) / "binaries" / host / slug
return str(path)

def global_blob_install_root(self, slug: str) -> str:
path = pathlib.Path(self.ensure_data_dir()) / "blobs" / slug
return str(path)

def lookup_binary_install_dir(self, host: str, slug: str) -> PathLike | None:
for data_dir in BaseDirectory.load_data_paths(self.resource_name):
p = pathlib.Path(data_dir) / "binaries" / host / slug
Expand Down
13 changes: 8 additions & 5 deletions ruyi/ruyipkg/distfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@
from .checksum import Checksummer
from .fetch import BaseFetcher
from .pkg_manifest import DistfileDecl
from .unpack import do_unpack
from .unpack import do_unpack, do_unpack_or_symlink


class Distfile:
def __init__(
self,
url: str,
urls: list[str],
dest: str,
decl: DistfileDecl,
) -> None:
self.url = url
self.urls = urls
self.dest = dest
self.size = decl.size
self.csums = decl.checksums
Expand All @@ -40,7 +40,7 @@ def ensure(self) -> None:
return

# the file is already gone, re-fetch
log.D(f"re-fetching {self.url} to {self.dest}")
log.D(f"re-fetching {self.dest}")
return self.fetch_and_ensure_integrity()

log.W(
Expand All @@ -61,7 +61,7 @@ def ensure_integrity_or_rm(self) -> bool:
return False

def fetch_and_ensure_integrity(self, *, resume: bool = False) -> None:
fetcher = BaseFetcher.new(self.url, self.dest)
fetcher = BaseFetcher.new(self.urls, self.dest)
fetcher.fetch(resume=resume)

if not self.ensure_integrity_or_rm():
Expand All @@ -71,3 +71,6 @@ def fetch_and_ensure_integrity(self, *, resume: bool = False) -> None:

def unpack(self, root: str | None) -> None:
return do_unpack(self.dest, root, self.strip_components)

def unpack_or_symlink(self, root: str | None) -> None:
return do_unpack_or_symlink(self.dest, root, self.strip_components)
51 changes: 35 additions & 16 deletions ruyi/ruyipkg/fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@


class BaseFetcher:
def __init__(self, url: str, dest: str) -> None:
self.url = url
def __init__(self, urls: list[str], dest: str) -> None:
self.urls = urls
self.dest = dest

@classmethod
Expand All @@ -16,12 +16,25 @@ def is_available(cls) -> bool:
return False

@abc.abstractmethod
def fetch_one(self, url: str, dest: str, resume: bool) -> bool:
return False

def fetch(self, *, resume: bool = False) -> None:
raise NotImplementedError
for url in self.urls:
log.I(f"downloading {url} to {self.dest}")
success = self.fetch_one(url, self.dest, resume)
if success:
return
# add retry logic if necessary; right now this is not needed because
# all fetcher commands handle retrying for us
# all URLs have been tried and all have failed
raise RuntimeError(
f"failed to fetch '{self.dest}': all source URLs have failed"
)

@classmethod
def new(cls, url: str, dest: str) -> Self:
return get_usable_fetcher_cls()(url, dest)
def new(cls, urls: list[str], dest: str) -> "BaseFetcher":
return get_usable_fetcher_cls()(urls, dest)


KNOWN_FETCHERS: list[type[BaseFetcher]] = []
Expand Down Expand Up @@ -55,8 +68,8 @@ def get_usable_fetcher_cls() -> type[BaseFetcher]:


class CurlFetcher(BaseFetcher):
def __init__(self, url: str, dest: str) -> None:
super().__init__(url, dest)
def __init__(self, urls: list[str], dest: str) -> None:
super().__init__(urls, dest)

@classmethod
def is_available(cls) -> bool:
Expand All @@ -68,7 +81,7 @@ def is_available(cls) -> bool:
log.D(f"exception occurred when trying to curl --version:", e)
return False

def fetch(self, *, resume: bool = False) -> None:
def fetch_one(self, url: str, dest: str, resume: bool) -> bool:
argv = ["curl"]
if resume:
argv.extend(("-C", "-"))
Expand All @@ -80,24 +93,27 @@ def fetch(self, *, resume: bool = False) -> None:
"60",
"--ftp-pasv",
"-o",
self.dest,
self.url,
dest,
url,
)
)

retcode = subprocess.call(argv)
if retcode != 0:
raise RuntimeError(
log.W(
f"failed to fetch distfile: command '{' '.join(argv)}' returned {retcode}"
)
return False

return True


register_fetcher(CurlFetcher)


class WgetFetcher(BaseFetcher):
def __init__(self, url: str, dest: str) -> None:
super().__init__(url, dest)
def __init__(self, urls: list[str], dest: str) -> None:
super().__init__(urls, dest)

@classmethod
def is_available(cls) -> bool:
Expand All @@ -109,18 +125,21 @@ def is_available(cls) -> bool:
log.D(f"exception occurred when trying to wget --version:", e)
return False

def fetch(self, *, resume: bool = False) -> None:
def fetch_one(self, url: str, dest: str, resume: bool) -> bool:
# These arguments are taken from Gentoo
argv = ["wget"]
if resume:
argv.append("-c")
argv.extend(("-t", "3", "-T", "60", "--passive-ftp", "-O", self.dest, self.url))
argv.extend(("-t", "3", "-T", "60", "--passive-ftp", "-O", dest, url))

retcode = subprocess.call(argv)
if retcode != 0:
raise RuntimeError(
log.W(
f"failed to fetch distfile: command '{' '.join(argv)}' returned {retcode}"
)
return False

return True


register_fetcher(WgetFetcher)
Loading

0 comments on commit 0061c08

Please sign in to comment.