Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
dac06e3
feat(build): Add `build` module and `BuildMetadata` class + tests
Ishirui Sep 11, 2025
6e8f1c9
feat(build): Use UUIDs for `BuildMetadata` IDs
Ishirui Sep 16, 2025
cea5443
feat(build): Add `BuildMetadata.to_file` method
Ishirui Sep 16, 2025
a64d548
feat(build): Add `file_hash` to `BuildMetadata` schema
Ishirui Sep 16, 2025
709f50b
feat(git): Add `__len__` and `__bool__` to `ChangeSet`
Ishirui Oct 13, 2025
a6a463b
feat(build): Add `BuildMetadata.get_canonical_filename` method
Ishirui Sep 16, 2025
d2d90e8
feat(build): Add `ANY` values to `OS` and `Arch` enums
Ishirui Sep 16, 2025
6ee94f4
feat(build): Default `to_file` to writing to a canonical filename
Ishirui Oct 13, 2025
5f5c55b
refactor(build): Allow passing build components as arguments to `Buil…
Ishirui Oct 13, 2025
6cf4ade
feat(build): Include worktree hash digest in canonical filename
Ishirui Oct 13, 2025
1075e38
refactor(build): Make `Platform` a fully-fledged `Struct`
Ishirui Oct 13, 2025
c37d12e
refactor(build): Remove useless enum values + rename `ArtifactFormat.…
Ishirui Oct 20, 2025
7597230
refactor(build): Move source info and uuid to end of `get_canonical_f…
Ishirui Oct 20, 2025
67227fa
feat(fs): Create `Path.hexdigest` method + tests + use it for metadat…
Ishirui Oct 20, 2025
daf4cb4
refactor(metadata): Use separate `ComponentFormat` and `DistributionF…
Ishirui Oct 20, 2025
d87f332
fix(build): Encode `artifact_format` as raw `str`
Ishirui Oct 21, 2025
32955b1
refactor(build): Use an encodable wrapper class instead
Ishirui Oct 21, 2025
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 src/dda/build/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SPDX-FileCopyrightText: 2025-present Datadog, Inc. <[email protected]>
#
# SPDX-License-Identifier: MIT
3 changes: 3 additions & 0 deletions src/dda/build/metadata/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SPDX-FileCopyrightText: 2025-present Datadog, Inc. <[email protected]>
#
# SPDX-License-Identifier: MIT
207 changes: 207 additions & 0 deletions src/dda/build/metadata/enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# SPDX-FileCopyrightText: 2025-present Datadog, Inc. <[email protected]>
#
# SPDX-License-Identifier: MIT

from enum import StrEnum, auto
from typing import Any, ClassVar

from msgspec import Struct

from dda.types.hooks import register_type_hooks


class ArtifactType(StrEnum):
"""
The type of a build artifact: either a "component" or a "distribution".
Each artifact type has a corresponding format enum.
"""

COMP = auto()
DIST = auto()


class DistributionFormat(StrEnum):
"""
The format of a "distribution"-type build artifact.
"""

DEB = auto()
RPM = auto()
MSI = auto()
OCI = auto()

def get_file_identifier(self) -> str:
"""
Get the file identifier for the artifact format.
This is the string that will be used to identify the artifact format in the filename.
This usually corresponds to the file extension, but can include some other charactres before it.
"""
match self:
case self.DEB:
return ".deb"
case self.RPM:
return ".rpm"
case self.MSI:
return ".msi"
case self.OCI:
return "-oci.tar.gz"
# Adding a default return value to satisfy mypy, even though we should never reach here
return ""

# Need to make a property function so it works with enums
@property
def type(self) -> ArtifactType:
"""The artifact type."""
return ArtifactType.DIST

def wrap(self) -> "ArtifactFormat":
return ArtifactFormat(enum=self)


class ComponentFormat(StrEnum):
"""
The format of a "component"-type build artifact.
"""

BIN = auto()

def get_file_identifier(self) -> str: # noqa:PLR6301
"""See `DistributionFormat.get_file_identifier` for more details."""
return "" # Binary files don't have a specific identifier

@property
def type(self) -> ArtifactType:
"""The artifact type."""
return ArtifactType.COMP

def wrap(self) -> "ArtifactFormat":
return ArtifactFormat(enum=self)


# Need custom logic for encoding/decoding, since msgspec does not support encoding StrEnum unions
class ArtifactFormat:
def __init__(self, enum: ComponentFormat | DistributionFormat):
self.enum = enum

@staticmethod
def encode(obj: "ArtifactFormat") -> dict[str, str]:
return {
"type": obj.enum.type.value,
"format": obj.enum.value,
}

@staticmethod
def decode(data: dict[str, str]) -> "ArtifactFormat":
match data["type"]:
case ArtifactType.COMP.value:
return ArtifactFormat(enum=ComponentFormat(data["format"]))
case ArtifactType.DIST.value:
return ArtifactFormat(enum=DistributionFormat(data["format"]))
case _:
msg = f"Invalid artifact type: {data['type']}"
raise ValueError(msg)

# Proxy methods and attributes to the underlying enum
def __getattr__(self, name: str) -> Any:
return getattr(self.enum, name)

def __eq__(self, other: object) -> bool:
return self.enum == other.enum if isinstance(other, ArtifactFormat) else self.enum == other

def __hash__(self) -> int:
return hash(self.enum)


class OS(StrEnum):
"""
The operating system for which the build artifact is intended.
"""

LINUX = auto()
WINDOWS = auto()
MACOS = auto()

# Any OS - used for indicating compatibility with any operating system
ANY = auto()

@classmethod
def from_alias(cls, alias: str) -> "OS":
"""
Get the OS enum value from an alias.
"""
match alias.lower():
case "linux":
return cls.LINUX
case "windows" | "nt" | "win":
return cls.WINDOWS
case "macos" | "darwin" | "osx":
return cls.MACOS
case "any":
return cls.ANY
case _:
msg = f"Invalid OS identifier: {alias}"
raise ValueError(msg)


class Arch(StrEnum):
"""
The CPU architecture for which the build artifact is intended.
"""

# x86 architectures - canonical name is amd64
AMD64 = auto()

# ARM architectures - canonical name is arm64
ARM64 = auto()

# ARMHF architectures
ARMHF = auto()

# Any architecture - used for indicating compatibility with any CPU architecture
ANY = auto()

@classmethod
def from_alias(cls, alias: str) -> "Arch":
"""
Get the Arch enum value from an alias.
"""
match alias.lower():
case "amd64" | "x86_64" | "x86-64" | "x86" | "x64":
return cls.AMD64
case "arm64" | "aarch64" | "arm" | "aarch":
return cls.ARM64
case "any":
return cls.ANY
case _:
msg = f"Invalid Arch identifier: {alias}"
raise ValueError(msg)


class Platform(Struct, frozen=True):
"""
The platform for which the build artifact is intended.
"""

os: OS
arch: Arch

ANY: ClassVar["Platform"]

@classmethod
def from_alias(cls, os_alias: str, arch_alias: str) -> "Platform":
"""
Get the Platform enum value from an alias.
"""
return cls(os=OS.from_alias(os_alias), arch=Arch.from_alias(arch_alias))

def __str__(self) -> str:
"""
Get the string representation of the platform.
"""
return f"{self.os}-{self.arch}"


# Initialize the ANY class variable after the class is fully defined
Platform.ANY = Platform(os=OS.ANY, arch=Arch.ANY)

register_type_hooks(ArtifactFormat, encode=ArtifactFormat.encode, decode=ArtifactFormat.decode)
Loading
Loading