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
3 changes: 3 additions & 0 deletions .github/workflows/node.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,6 @@ jobs:

- name: Run checks
run: poetry run poe check

- name: Run integration tests
run: poetry run poe check-integration
36 changes: 20 additions & 16 deletions node/flatpak_node_generator/cache.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
from __future__ import annotations

import os
import re
import tempfile
import types
from collections.abc import Iterator
from pathlib import Path
from typing import IO, Iterator, Optional, Type
from typing import IO


class Cache:
instance: 'Cache'
instance: Cache

@classmethod
def get_working_instance_if(cls, condition: bool) -> 'Cache':
def get_working_instance_if(cls, condition: bool) -> Cache:
return cls.instance if condition else NullCache()

class BucketReader:
Expand All @@ -23,14 +26,14 @@ def read_all(self) -> bytes:
def close(self) -> None:
raise NotImplementedError

def __enter__(self) -> 'Cache.BucketReader':
def __enter__(self) -> Cache.BucketReader:
return self

def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_value: Optional[BaseException],
traceback: Optional[types.TracebackType],
exc_type: type[BaseException] | None,
exc_value: BaseException | None,
traceback: types.TracebackType | None,
) -> None:
self.close()

Expand All @@ -44,14 +47,14 @@ def cancel(self) -> None:
def seal(self) -> None:
raise NotImplementedError

def __enter__(self) -> 'Cache.BucketWriter':
def __enter__(self) -> Cache.BucketWriter:
return self

def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_value: Optional[BaseException],
traceback: Optional[types.TracebackType],
exc_type: type[BaseException] | None,
exc_value: BaseException | None,
traceback: types.TracebackType | None,
) -> None:
if traceback is None:
self.seal()
Expand All @@ -62,10 +65,10 @@ class BucketRef:
def __init__(self, key: str) -> None:
self.key = key

def open_read(self) -> Optional['Cache.BucketReader']:
def open_read(self) -> Cache.BucketReader | None:
raise NotImplementedError

def open_write(self) -> 'Cache.BucketWriter':
def open_write(self) -> Cache.BucketWriter:
raise NotImplementedError

def get(self, key: str) -> BucketRef:
Expand All @@ -87,7 +90,7 @@ class NullBucketRef(Cache.BucketRef):
def __init__(self, key: str) -> None:
super().__init__(key)

def open_read(self) -> Optional[Cache.BucketReader]:
def open_read(self) -> Cache.BucketReader | None:
return None

def open_write(self) -> Cache.BucketWriter:
Expand All @@ -101,7 +104,7 @@ class FilesystemBasedCache(Cache):
_SUBDIR = 'flatpak-node-generator'
_KEY_CHAR_ESCAPE_RE = re.compile(r'[^A-Za-z0-9._\-]')

def __init__(self, cache_root: Optional[Path] = None) -> None:
def __init__(self, cache_root: Path | None = None) -> None:
self._cache_root = cache_root or self._default_cache_root()

@staticmethod
Expand Down Expand Up @@ -152,7 +155,8 @@ def __init__(self, key: str, cache_root: Path) -> None:

self._cache_path = self._cache_root / FilesystemBasedCache._escape_key(key)

def open_read(self) -> Optional[Cache.BucketReader]:
def open_read(self) -> Cache.BucketReader | None:

try:
fp = self._cache_path.open('rb')
except FileNotFoundError:
Expand Down
21 changes: 12 additions & 9 deletions node/flatpak_node_generator/electron.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from __future__ import annotations

import hashlib
import os.path
import urllib.parse
from typing import Dict, Iterator, NamedTuple, Optional
from collections.abc import Iterator
from typing import ClassVar, NamedTuple

from .integrity import Integrity
from .package import SemVer
Expand All @@ -18,7 +21,7 @@ class Binary(NamedTuple):
url: str
integrity: Integrity

arch: Optional['ElectronBinaryManager.Arch'] = None
arch: ElectronBinaryManager.Arch | None = None

@property
def url_hash(self) -> str:
Expand All @@ -28,7 +31,7 @@ def url_hash(self) -> str:
)
return hashlib.sha256(url_dir.encode()).hexdigest()

ELECTRON_ARCHES_TO_FLATPAK = {
ELECTRON_ARCHES_TO_FLATPAK: ClassVar[dict[str, str]] = {
'ia32': 'i386',
'x64': 'x86_64',
'armv7l': 'arm',
Expand All @@ -38,7 +41,7 @@ def url_hash(self) -> str:
INTEGRITY_BASE_FILENAME = 'SHASUMS256.txt'

def __init__(
self, version: str, base_url: str, integrities: Dict[str, Integrity]
self, version: str, base_url: str, integrities: dict[str, Integrity]
) -> None:
self.version = version
self.base_url = base_url
Expand All @@ -47,7 +50,7 @@ def __init__(
def child_url(self, child: str) -> str:
return f'{self.base_url}/{child}'

def find_binaries(self, binary: str) -> Iterator['ElectronBinaryManager.Binary']:
def find_binaries(self, binary: str) -> Iterator[ElectronBinaryManager.Binary]:
for electron_arch, flatpak_arch in self.ELECTRON_ARCHES_TO_FLATPAK.items():
# Electron v19+ drop linux-ia32 support.
if (
Expand All @@ -70,7 +73,7 @@ def find_binaries(self, binary: str) -> Iterator['ElectronBinaryManager.Binary']
)

@property
def integrity_file(self) -> 'ElectronBinaryManager.Binary':
def integrity_file(self) -> ElectronBinaryManager.Binary:
return ElectronBinaryManager.Binary(
filename=f'SHASUMS256.txt-{self.version}',
url=self.child_url(self.INTEGRITY_BASE_FILENAME),
Expand All @@ -79,8 +82,8 @@ def integrity_file(self) -> 'ElectronBinaryManager.Binary':

@staticmethod
async def for_version(
version: str, *, base_url: Optional[str] = None
) -> 'ElectronBinaryManager':
version: str, *, base_url: str | None = None
) -> ElectronBinaryManager:
if base_url is None:
base_url = (
f'https://github.com/electron/electron/releases/download/v{version}'
Expand All @@ -91,7 +94,7 @@ async def for_version(
await Requests.instance.read_all(integrity_url, cachable=True)
).decode()

integrities: Dict[str, Integrity] = {}
integrities: dict[str, Integrity] = {}
for line in integrity_data.splitlines():
digest, star_filename = line.split()
filename = star_filename.strip('*')
Expand Down
14 changes: 8 additions & 6 deletions node/flatpak_node_generator/integrity.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,36 @@
from __future__ import annotations

import base64
import binascii
import hashlib
from typing import Any, NamedTuple, Union
from typing import Any, NamedTuple


class Integrity(NamedTuple):
algorithm: str
digest: str

@staticmethod
def parse(value: str) -> 'Integrity':
def parse(value: str) -> Integrity:
algorithm, encoded_digest = value.split('-', 1)
assert algorithm.startswith('sha'), algorithm
digest = binascii.hexlify(base64.b64decode(encoded_digest)).decode()

return Integrity(algorithm, digest)

@staticmethod
def from_sha1(sha1: str) -> 'Integrity':
def from_sha1(sha1: str) -> Integrity:
assert len(sha1) == 40, f'Invalid length of sha1: {sha1}'
return Integrity('sha1', sha1)

@staticmethod
def generate(data: Union[str, bytes], *, algorithm: str = 'sha256') -> 'Integrity':
def generate(data: str | bytes, *, algorithm: str = 'sha256') -> Integrity:
builder = IntegrityBuilder(algorithm)
builder.update(data)
return builder.build()

@staticmethod
def from_json_object(data: Any) -> 'Integrity':
def from_json_object(data: Any) -> Integrity:
return Integrity(algorithm=data['algorithm'], digest=data['digest'])

def to_json_object(self) -> Any:
Expand All @@ -43,7 +45,7 @@ def __init__(self, algorithm: str = 'sha256') -> None:
self.algorithm = algorithm
self._hasher = hashlib.new(algorithm)

def update(self, data: Union[str, bytes]) -> None:
def update(self, data: str | bytes) -> None:
data_bytes: bytes
if isinstance(data, str):
data_bytes = data.encode()
Expand Down
55 changes: 32 additions & 23 deletions node/flatpak_node_generator/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import json
import os
import sys
from collections.abc import Iterator
from pathlib import Path
from typing import Iterator, List, Set

from .cache import Cache, FilesystemBasedCache
from .manifest import DEFAULT_SPLIT_SIZE_KB, ManifestGenerator
Expand All @@ -18,7 +18,7 @@
from .requests import Requests, StubRequests


def _scan_for_lockfiles(base: Path, patterns: List[str]) -> Iterator[Path]:
def _scan_for_lockfiles(base: Path, patterns: list[str]) -> Iterator[Path]:
for root, _, files in os.walk(base.parent):
if base.name in files:
lockfile = Path(root) / base.name
Expand Down Expand Up @@ -170,7 +170,7 @@ async def _async_main() -> None:
if not args.no_requests_cache:
Cache.instance = FilesystemBasedCache()

lockfiles: List[Path]
lockfiles: list[Path]
if args.recursive or args.recursive_pattern:
lockfiles = list(
_scan_for_lockfiles(Path(args.lockfile), args.recursive_pattern)
Expand Down Expand Up @@ -200,8 +200,8 @@ async def _async_main() -> None:
assert False, args.type

print('Reading packages from lockfiles...')
packages: Set[Package] = set()
rcfile_node_headers: Set[NodeHeaders] = set()
packages: set[Package] = set()
rcfile_node_headers: set[NodeHeaders] = set()

for lockfile in lockfiles:
lockfile_provider = provider_factory.create_lockfile_provider()
Expand Down Expand Up @@ -234,13 +234,16 @@ async def _async_main() -> None:
)
special = SpecialSourceProvider(gen, options)

with provider_factory.create_module_provider(gen, special) as module_provider:
with GeneratorProgress(
with (
provider_factory.create_module_provider(gen, special) as module_provider,
GeneratorProgress(
packages,
module_provider,
args.max_parallel,
) as progress:
await progress.run()
) as progress,
):
await progress.run()

for headers in rcfile_node_headers:
print(f'Generating headers {headers.runtime} @ {headers.target}')
await special.generate_node_headers(headers)
Expand Down Expand Up @@ -268,26 +271,32 @@ async def _async_main() -> None:
for i, part in enumerate(gen.split_sources()):
output = Path(args.output)
output = output.with_suffix(f'.{i}{output.suffix}')
with open(output, 'w', encoding='utf-8') as fp:
json.dump(part, fp, indent=ManifestGenerator.JSON_INDENT)
await asyncio.to_thread(
output.write_text,
json.dumps(part, indent=ManifestGenerator.JSON_INDENT),
encoding='utf-8',
)
delattr(gen, '_upgraded_sources')
print(f'Wrote {gen.source_count} to {i + 1} file(s).')
else:
sources = list(gen.ordered_sources())
await Requests.instance.upgrade_to_sha256(sources)
with open(args.output, 'w', encoding='utf-8') as fp:
json.dump(
sources,
fp,
indent=ManifestGenerator.JSON_INDENT,
output_path = Path(args.output)
data = json.dumps(
sources,
indent=ManifestGenerator.JSON_INDENT,
)
await asyncio.to_thread(
output_path.write_text,
data,
encoding='utf-8',
)
if len(data.encode('utf-8')) >= gen.split_size:
print(
'WARNING: generated-sources.json is too large for GitHub.',
file=sys.stderr,
)

if fp.tell() >= gen.split_size:
print(
'WARNING: generated-sources.json is too large for GitHub.',
file=sys.stderr,
)
print(' (Pass -s to enable splitting.)')
print(' (Pass -s to enable splitting.)')

print(f'Wrote {gen.source_count} source(s).')

Expand Down
Loading