📦 Builds e Empacotamento#

Esta página descreve como empacotar o Codis para distribuição.


Script de Empacotamento#

O Codis inclui o script package_codis.py na raiz do projeto para gerar distribuições.

python package_codis.py

setup.py#

O setup.py na raiz define os metadados do pacote Python. A versão não é hardcoded - é extraída dinamicamente de APP_VERSION em src/constants.py via regex, para nunca ficar dessincronizada:

# setup.py (resumido)
import re
from pathlib import Path
from setuptools import setup, find_packages

def _find_version() -> str:
    """Extrai APP_VERSION de src/constants.py."""
    m = re.search(r'^APP_VERSION\s*=\s*["\']([^"\']+)["\']',
                  Path("src/constants.py").read_text(encoding="utf-8"), re.MULTILINE)
    return m.group(1)

setup(
    name="codis",
    version=_find_version(),
    python_requires=">=3.12",
    packages=find_packages(exclude=["tests*", "docs*", "wiki*", "build*", "dist*"]),
    install_requires=[],           # sem dependências obrigatórias
    extras_require={
        "recommended": ["send2trash>=1.8.0", "Pillow>=9.0.0", "markdown>=3.4.0"],
        "full": ["send2trash>=1.8.0", "Pillow>=9.0.0", "markdown>=3.4.0",
                 "py7zr>=0.20.0", "rarfile>=4.0"],
        "dev": ["pyinstaller", "pytest", "pytest-cov", "flake8", "pylint"],
    },
    entry_points={
        "console_scripts": ["codis = run:main"],
        "gui_scripts": ["codis-gui = run:main"],
    },
)

pip install .[dev] instala as ferramentas de desenvolvimento (PyInstaller, pytest, flake8, pylint) de uma vez.

Instalação via pip (modo desenvolvimento)#

pip install -e .

Após isso, o comando codis fica disponível globalmente.


Dependências#

O requirements.txt lista pacotes opcionais que habilitam funcionalidades extras:

send2trash>=1.8.0    # lixeira nativa (fallback: pseudo-lixeira interna)
Pillow>=9.0.0        # imagens no K-UI
markdown>=3.4.0      # preview Markdown
py7zr>=0.20.0        # suporte a .7z
rarfile>=4.0         # suporte a .rar

Nenhum deles é obrigatório. O Codis executa com zero dependências externas.


Empacotamento com PyInstaller#

O Codis inclui um modo dedicado em package_codis.py que monta automaticamente todos os parâmetros corretos do PyInstaller - é a forma recomendada de gerar um executável, em vez de chamar pyinstaller manualmente:

pip install pyinstaller
python package_codis.py pyinstaller            # gera em modo --onedir (padrão)
python package_codis.py pyinstaller --onefile  # executável único em vez de pasta
python package_codis.py pyinstaller --debug    # mantém console para depuração

O padrão é um executável único (--onefile, mesmo modo do CI); use --onedir para gerar uma pasta (inicia mais rápido, pois não auto-extrai a cada execução).

Por baixo dos panos, o script monta automaticamente:

  • Dados incluídos: os src/locales/<idioma>.json (14 idiomas; o .translate_manifest.json de desenvolvimento não entra), wiki/ e requirements.txt
  • Tcl/Tk: coletado pelo hook oficial do PyInstaller. Não adicione --add-data de Tcl/Tk manualmente (duplica os dados e, sem DISPLAY, pode embutir o prefixo inteiro do Python)
  • Imports ocultos: todos os submódulos de tkinter, mais src.plugins, src.ia, src.lsp, src.lint, src.syntax, src.diff, xmlrpc.client/server e email.mime.*
  • Módulos excluídos (reduzem o tamanho do bundle): matplotlib, numpy, pandas, scipy, cv2, PyQt5/6, PySide2/6, wx, entre outros nunca usados pelo Codis

Para nunca usar UPX (como no CI): python package_codis.py pyinstaller --no-upx ou CODIS_NO_UPX=1. O PyInstaller já ignora o UPX fora do Windows, e no executável --onefile final ele não reduz nada.

Tamanho de referência do executável Linux x64: ~32 MB. O CI falha se passar de 60 MB.

Se send2trash estiver instalado no ambiente de build, ele é incluído automaticamente no bundle. Sem ele, o executável final usa a lixeira do projeto (.codis_trash/) normalmente.


Empacotamento com Nuitka (alternativa não-oficial)#

Diferente do PyInstaller, o Nuitka não faz parte do pipeline oficial do Codis (não há workflow de CI usando-o, nem script dedicado em package_codis.py). O comando abaixo é um ponto de partida não usado pelo projeto, caso prefira essa alternativa por conta própria.

pip install nuitka
python -m nuitka --onefile --enable-plugin=tk-inter \
    --include-data-dir=src/locales=src/locales \
    --include-data-dir=wiki=wiki \
    run.py

GitHub Actions#

O repositório inclui 10 workflows em .github/workflows/. A regra de ouro: cada commit passa por um único pipeline (pipeline.yml), que valida, compila e publica a release uma só vez - os arquivos compilados são os mesmos do artefato e da release, e nada é refeito quando você lança uma versão.

O pipeline (commit → build → release)#

push / tag / PR
      │
   plan ─────────────┬──► quality  (testes, lint, i18n)  ──┐
 (decide tudo        │                                      ├──► release ──► summary
  uma única vez)     └──► build    (executáveis + ZIP)    ──┘
Job O que faz
plan Roda .github/scripts/plan.py: lê APP_VERSION, descobre o canal de release, o que mudou desde o último build publicado e monta a matriz de plataformas
quality Chama _quality.yml: sintaxe (Python 3.12/3.13), imports em Linux/Windows/macOS, Flake8 e Pylint (informativos), pytest com cobertura, i18n e consistência de locales
build Chama _build.yml: executáveis PyInstaller de cada plataforma (com smoke test no Linux e limite de 60 MB) + ZIP do código-fonte. Roda em paralelo com quality
release Só roda se quality e build passaram. Baixa os arquivos já compilados, gera SHA256SUMS.txt e as notas e publica a release. Não recompila nada
summary Resumo da execução; falha o pipeline se alguma etapa obrigatória falhou

Canais de release#

O canal é decidido pelo plan, sem nenhuma ação manual:

Canal Quando Resultado
stable Push em main/master quando APP_VERSION ainda não tem tag; ou push de tag vX.Y.Z Release oficial vX.Y.Z (marcada como Latest; -beta.N etc. viram pre-release), com notas do CHANGELOG.md e notas automáticas por label
nightly Push em main/master quando a versão já foi lançada Uma única pré-release móvel chamada nightly, atualizada a cada commit com mudança de código (não acumula releases)
dev Push em develop Pré-release móvel dev (somente as 3 plataformas principais)
none Pull request Só valida e compila (3 plataformas principais); não publica

Cada release traz, de uma vez: os executáveis (Windows x64, Linux x64, macOS Apple Silicon e, em main/tag, também Linux ARM64, Windows ARM64/x86 e macOS Intel, que são experimentais e nunca derrubam o pipeline), o ZIP do código-fonte (Codis-vX.Y.Z-source.zip), o SHA256SUMS.txt (usado pelo atualizador do app para conferir a integridade), as notas e o comparativo com a versão anterior.

Releases oficiais são criadas como rascunho, recebem todos os arquivos e só então são publicadas: nem usuários nem o atualizador enxergam uma release pela metade. Pré-releases nightly/dev não afetam o atualizador (ele lê apenas a última release oficial).

Commits só de documentação (*.md, wiki/, docs/, tools/...) não compilam nem publicam release: o plan compara o commit com o último build publicado e só segue se algo que entra no binário mudou (src/**, run.py, requirements.txt, package_codis.py, setup.py, pyproject.toml ou os scripts/workflows de build).

Todos os workflows#

Arquivo Trigger O que faz
pipeline.yml Push em main/master/develop, tag v*.*.*, PR para main/master, workflow_dispatch (inputs release e extended) Porta de entrada única: plan → quality ∥ build → release → summary
_quality.yml Chamado pelo pipeline.yml Verificações de código (reutilizável, sem triggers próprios)
_build.yml Chamado pelo pipeline.yml Executáveis multiplataforma + ZIP de código-fonte (reutilizável, não publica)
docs.yml PR com mudanças em docs/código (check); push em main com mudanças em docs/**, wiki/**, *.md, tools/** ou src/constants.py (site + deploy + wiki); workflow_dispatch PR: confere tools/docs_facts.py --check e tools/build_site.py --check. Push: gera docs/ e publica no GitHub Pages; sincroniza wiki/ com a GitHub Wiki somente se wiki/ mudou
codeql.yml Push/PR em main/master/develop com mudanças de código, e semanalmente (segunda 08:00 UTC) Análise estática de segurança CodeQL (independente do pipeline; não bloqueia releases)
dependency-review.yml PR com mudanças em requirements.txt ou .github/dependabot.yml Bloqueia PRs com dependências de CVE alto/crítico e comenta o resumo no PR
label.yml Abertura/edição/sync de PR, abertura/edição de issue Rotulagem automática (as labels alimentam as notas de release por categoria)
locale-sync.yml Somente workflow_dispatch (flag apply) Ferramenta manual: sincroniza as chaves dos 14 locales e commita. A verificação automática já roda em quality
artifact-cleanup.yml Semanalmente (segunda 07:00 UTC), ou workflow_dispatch (dry_run, min_age_days, keep_last_builds) Remove artifacts antigos do Actions, protegendo as execuções mais recentes de pipeline.yml
stale.yml Diariamente (06:00 UTC), ou workflow_dispatch Marca issues/PRs inativos como stale

As categorias das notas automáticas ficam em .github/release.yml (por label: bug, melhoria, segurança, documentação, i18n, dependencies etc.).

Disparo manual#

Em Actions → Pipeline → Run workflow:

  • release = auto (padrão): comporta-se como um push no branch escolhido, ignorando o filtro de "o que mudou".
  • release = none: valida e compila sem publicar (útil para testar o build).
  • extended = false: só as 3 plataformas principais.
  • Rodar a partir de uma tag (Use workflow from: Tag: vX.Y.Z) recompila e atualiza os arquivos da release dessa tag (reexecução segura, sem duplicar).

Onde cada coisa roda (para não duplicar)#

Tarefa Onde roda Onde não roda mais
Checagem de sintaxe quality (uma vez) no build (antes havia outra)
ZIP do código-fonte build (uma vez) package.yml (removido)
Compilar executáveis build (uma vez por commit) release.yml (recompilava ao criar a tag)
Verificar locales quality locale-sync.yml em push/PR
Site, wiki e conferência de docs docs.yml três workflows separados

Mapa do que mudou: ci.yml → _quality.yml; build.yml + package.yml + release.yml → _build.yml + pipeline.yml; docs-check.yml + pages.yml + wiki-sync.yml → docs.yml. O badge de CI do README aponta para pipeline.yml.


Testes#

Os testes ficam em tests/ - 70 arquivos, 1683 casos no total, rodando 100% headless (sem display gráfico):

tests/
├── conftest.py              ← Fixtures compartilhadas + stub de Tkinter
├── test_config.py           ← Testes do ConfigManager
├── test_diff_engine.py      ← Testes do DiffEngine
├── test_lsp_protocol.py     ← Testes do protocolo LSP
├── test_syntax_patterns.py  ← Testes de highlight (8 linguagens)
└── ... (mais 13 arquivos)

Executar todos os testes:

pytest tests/ -v
pytest tests/ --cov=src --cov-report=term-missing   # com cobertura

→ Veja Testes para a cobertura completa por módulo, fixtures disponíveis e convenções.


Distribuição como ZIP Portátil#

O Codis pode ser distribuído como um ZIP portátil (sem instalação):

  1. Clone o repositório.
  2. Compacte o diretório: zip -r Codis.zip .
  3. O usuário extrai e executa python run.py - sem nenhum pip install.

Para funcionalidades extras (lixeira nativa, imagens, etc.):

pip install send2trash Pillow markdown

Versionamento#

O Codis usa Semantic Versioning (SemVer):

  • MAJOR.MINOR.PATCH - ex: 1.0.0
  • A versão é definida em src/constants.py (APP_VERSION)
  • O codinome de cada versão é definido em APP_CODENAME (ex: "Aether")

Para lançar uma versão não é preciso criar tag nem rodar nada manualmente:

  1. Atualize CHANGELOG.md (seção ## [X.Y.Z]) e APP_VERSION em src/constants.py.
  2. Faça o commit/merge em main.

O pipeline.yml percebe que vX.Y.Z ainda não existe, valida, compila todas as plataformas, gera o ZIP do código-fonte e os checksums e publica a release oficial - tudo na mesma execução. Os commits seguintes (sem mudar a versão) apenas atualizam a pré-release nightly.

Também continua válido criar a tag manualmente (git tag vX.Y.Z && git push origin vX.Y.Z): ela precisa ser igual a APP_VERSION, e se a release já tiver sido publicada o pipeline não faz nada.

Requisitos de runtime do executável compilado#

O binário gerado por package_codis.py pyinstaller não embute Python para rodar pip, venv, servidores LSP baseados em Python (pylsp, pyright...) nem o code runner de Python: eles usam o Python do sistema (ver Troubleshooting). Ao adicionar código que lança subprocessos Python, use src/pyexec.py (python_cmd(), child_env(), script_command()) - nunca sys.executable, que no executável compilado é o próprio Codis.