Skip to content
Open
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
84 changes: 84 additions & 0 deletions src/projspec/artifact/installable.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,87 @@ def clean(self):
if self.fn is not None:
self.proj.fs.rm(self.fn)
self.fn = None


class MacOSZipArtifact(FileArtifact):
"""A zipped macOS app artifact"""

def __init__(self, proj, fn=None, **kw):
super().__init__(proj=proj, fn=fn or f"{proj.url}/dist/*.app.zip", **kw)


class DMGArtifact(FileArtifact):
"""A macOS DMG artifact"""

def __init__(self, proj, fn=None, **kw):
super().__init__(proj=proj, fn=fn or f"{proj.url}/dist/*.dmg", **kw)


class PKGArtifact(FileArtifact):
"""A macOS PKG artifact"""

def __init__(self, proj, fn=None, **kw):
super().__init__(proj=proj, fn=fn or f"{proj.url}/dist/*.pkg", **kw)


class MSIArtifact(FileArtifact):
"""A Windows MSI artifact"""

def __init__(self, proj, fn=None, **kw):
super().__init__(proj=proj, fn=fn or f"{proj.url}/dist/*.msi", **kw)


class AABArtifact(FileArtifact):
"""An Android AAB artifact"""

def __init__(self, proj, fn=None, **kw):
super().__init__(proj=proj, fn=fn or f"{proj.url}/dist/*.aab", **kw)


class APKArtifact(FileArtifact):
"""An Android APK artifact"""

def __init__(self, proj, fn=None, **kw):
super().__init__(proj=proj, fn=fn or f"{proj.url}/dist/*.apk", **kw)


class IPAArtifact(FileArtifact):
"""An iOS IPA artifact"""

def __init__(self, proj, fn=None, **kw):
super().__init__(proj=proj, fn=fn or f"{proj.url}/dist/*.ipa", **kw)


class RPMArtifact(FileArtifact):
"""A Linux RPM artifact"""

def __init__(self, proj, fn=None, **kw):
super().__init__(proj=proj, fn=fn or f"{proj.url}/dist/*.rpm", **kw)


class DEBArtifact(FileArtifact):
"""A Linux DEB artifact"""

def __init__(self, proj, fn=None, **kw):
super().__init__(proj=proj, fn=fn or f"{proj.url}/dist/*.deb", **kw)


class LinuxPKGArtifact(FileArtifact):
"""A Linux PKG artifact"""

def __init__(self, proj, fn=None, **kw):
super().__init__(proj=proj, fn=fn or f"{proj.url}/dist/*.pkg.tar.zst", **kw)


class FlatpakArtifact(FileArtifact):
"""A Linux Flatpak artifact"""

def __init__(self, proj, fn=None, **kw):
super().__init__(proj=proj, fn=fn or f"{proj.url}/dist/*.flatpak", **kw)


class WebZipArtifact(FileArtifact):
"""A static website zipfile artifact"""

def __init__(self, proj, fn=None, **kw):
super().__init__(proj=proj, fn=fn or f"{proj.url}/dist/*.web.zip", **kw)
2 changes: 2 additions & 0 deletions src/projspec/proj/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from projspec.proj.base import ParseFailed, Project, ProjectSpec
from projspec.proj.briefcase import Briefcase
from projspec.proj.conda_package import CondaRecipe, RattlerRecipe
from projspec.proj.conda_project import CondaProject
from projspec.proj.documentation import RTD, MDBook
Expand All @@ -16,6 +17,7 @@
"ParseFailed",
"Project",
"ProjectSpec",
"Briefcase",
"CondaRecipe",
"CondaProject",
"GitRepo",
Expand Down
171 changes: 171 additions & 0 deletions src/projspec/proj/briefcase.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,179 @@
import platform
import sys

from projspec.proj import ProjectSpec
from projspec.utils import AttrDict


def supported(apps, app, *config):
"""Check if an app is supported on a given platform.

Looks in the metadata for app for a `supported` key in each
level named in config. If there isn't a table for the named config,
or the table contains a `supported = false` declaration, the

For example, `supported(apps, "foo", "linux", "system")` will check for:

* A "linux" table; if there isn't, return False
* A `supported` key in the "linux" table (defaulting True)
* A "linux.system" table; if there isn't return False
* A `supported` key in the "linux.system" table (defaulting True)

If any of those results return False, the app isn't supported.
"""
platform = apps[app].get(config[0], {"supported": False})
supported = platform.get("supported", True)
for part in config[1:]:
try:
platform = platform.get(part, {"supported": False})
supported &= platform.get("supported", True)
except KeyError:
# Platform config doesn't exist
supported = False

return supported


class Briefcase(ProjectSpec):
spec_doc = "https://briefcase.readthedocs.io/en/stable/reference/configuration.html"

def match(self) -> bool:
return "briefcase" in self.proj.pyproject.get("tool", {})

def parse(self) -> None:
from projspec.artifact.installable import (
AABArtifact,
APKArtifact,
DEBArtifact,
DMGArtifact,
IPAArtifact,
LinuxPKGArtifact,
MacOSZipArtifact,
MSIArtifact,
PKGArtifact,
RPMArtifact,
WebZipArtifact,
)

briefcase_meta = self.proj.pyproject["tool"]["briefcase"]

cont = AttrDict()
self._contents = cont

self._artifacts = AttrDict()

apps = briefcase_meta.get("app", {})

if sys.platform == "darwin":
for fmt, Artifact, arg in [
("macOS-app", MacOSZipArtifact, "zip"),
("macOS-dmg", DMGArtifact, "dmg"),
("macOS-pkg", PKGArtifact, "pkg"),
]:
for app in apps:
if supported(apps, app, "macOS"):
self._artifacts[fmt] = Artifact(
proj=self.proj,
cmd=["briefcase", "package", "-a", app, "-p", arg],
)

# iOS doesn't produce an artifact directly, but it's included for
# completeness.
for app in apps:
if supported(apps, app, "iOS"):
self._artifacts["iOS"] = IPAArtifact(
proj=self.proj,
cmd=["briefcase", "package", "iOS", "-a", app, "-p", "ipa"],
)

elif sys.platform == "linux":
# This only covers natively built packages; these can all be built
# via Docker as well.
release = platform.freedesktop_os_release()
release_id = release["ID"]
release_like = release.get("ID_LIKE", "")

for app in apps:
if release_id == "fedora" or "fedora" in release_like:
if supported(apps, app, "linux", "system", "rhel"):
self._artifacts["linux-rpm"] = Artifact(
proj=self.proj,
cmd=["briefcase", "package", "-a", app, "-p", "rpm"],
)
elif "suse" in release_like:
if supported(apps, app, "linux", "system", "suse"):
self._artifacts["linux-rpm"] = Artifact(
proj=self.proj,
cmd=["briefcase", "package", "-a", app, "-p", "rpm"],
)
elif release_id == "debian" or "debian" in release_like:
if supported(apps, app, "linux", "system", "debian"):
self._artifacts["linux-deb"] = Artifact(
proj=self.proj,
cmd=["briefcase", "package", "-a", app, "-p", "deb"],
)
elif release_id == "arch" or "arch" in release_like:
if supported(apps, app, "linux", "system", "arch"):
self._artifacts["linux-pkg"] = Artifact(
proj=self.proj,
cmd=["briefcase", "package", "-a", app, "-p", "pkg"],
)

if supported(apps, app, "linux", "flatpak"):
self._artifacts["linux-flatpak"] = IPAArtifact(
proj=self.proj,
cmd=[
"briefcase",
"package",
"linux",
"flatpak",
"-a",
app,
"-p",
"flatpak",
],
)

elif sys.platform == "windows":
for app in apps:
if supported(apps, app, "windows"):
self._artifacts["windows-msi"] = MSIArtifact(
proj=self.proj,
cmd=["briefcase", "package", "-a", app, "-p", "msi"],
)

# Android apps can be built on every platform
for app in apps:
if supported(apps, app, "android"):
for fmt, Artifact, arg in [
("android-aab", AABArtifact, "aab"),
("android-apk", APKArtifact, "apk"),
]:
self._artifacts[fmt] = Artifact(
proj=self.proj,
cmd=[
"briefcase",
"package",
"android",
"-a",
app,
"-p",
arg,
],
)

# Web apps can be built on every platform
for app in apps:
if supported(apps, app, "web"):
self._artifacts["web-zip"] = WebZipArtifact(
proj=self.proj,
cmd=[
"briefcase",
"package",
"web",
"-a",
app,
"-p",
"zip",
],
)