6 minutos
Software como bundle (.app) no macOS
Normalmente sistemas *nix possuem um modo bem efetivo de lidar com código duplicado, isolando código que se repete em um pacote. Então 1 ou mais pacotes podem utilizar esse código unindo forças para manutenção e expansão.
Mas como existem diversos softwares que podem utilizar o mesmo pacote e esse pacote pode apresentar diversas versões cada uma com limitações distintas, é muito comum utilizar um gerenciador de pacotes para cuidar destas relações e limitações simplificando a manutenção do sistema, tornando tarefas como instalar, atualizar, remover, entre outras relacionadas a pacotes muito mais fáceis.
Por exemplo, considerando uma distribuição baseada em Debian, o pacote é o .deb
, quem faz o gerenciamento destes pacotes é o Apt.
macOS apesar de ser baseado em um sistema Unix, não tem gerenciador de pacotes, muito por que o público alvo de produtos Apple majoritariamente procurar por experiências visuais e simples.
Também existe a ideia que o ecosistema open source de empacotamento de software é bem complexo demandando tempo, equipe, infraestrutura que custariam caro só pra vender um sistema ou um hardware, sem contar que seria pouco rentável. [1]
Com isso a Apple cria a Mac App Store, um modo fácil de rentabilizar a curadoria e a distribuição de softwares para macOS cobrando uma taxa dos desenvolvedores. Possui um amplo catálogo de software e o que não se encontra lá, pode ser obtido em arquivos:
.dmg
: sempre que é aberto no macOS é lido como um disco externo, que contém o software dentro;.pkg
: instalador muito parecido com encontrados em Windows e que tem várias etapas de instalação, muitas vezes se utiliza esse tipo de abordagem quando se precisa de permissão com nível de administrador para instalar certos componentes e scripts personalizados durante o processo.
Neste post será falado sobre o conteúdo de arquivos .dmg
, onde é entregue o app bundle, que é uma pasta com extensão .app
e organização interna padrão do macOS que é reconhecida como um app. A referência a bundle é sobre como são tratadas as dependencias que são todas enviadas dentro dessa pasta .app
que é um modo bem simples de resolver a falta de um gerenciador de pacotes.
Para efetuar a instalação somente se arrasta o <app-name>.app
para a pasta /Applications
e pronto, está feita a instalação.
Por mais que pareça estranho, carregar as dependências consigo e instalar todas junto, sem evitar a duplicação também é extremamente utilizado em Windows.
Mencionei que o <app-name>.app
possui uma estrutura interna que possui uma organização predefinida e agora vamos falar dela, vou demonstrar através do comando tree sua organização:
$ tree /Applications/<app-name>.app
<app-name>.app
└── Contents
├── CodeResources
├── Frameworks
│ └── [...]
├── Info.plist
├── Library
│ └── LaunchServices
│ └── com.daisydiskapp.DaisyDiskStandAlone.AdminHelper
├── MacOS
│ └── <app-name>
├── PkgInfo
├── Resources
│ ├── RandomIcon.pdf
│ ├── [...]
│ ├── dsa_pub.pem
│ └── en.lproj
│ ├── AboutWindow.nib
│ ├── AboutWindow.strings
│ ├── [..]
└── _CodeSignature
└── CodeResources
A estrutura <app-name>.app/Content
é feita para que seja identificada a versão mais moderna do bundle diferenciando de versões legadas.
Vamos entendendo e passando por arquivos mais comuns encontrados em bundles:
<app-name>.app/Contents/Frameworks
Já vimos que distribuir software como bundle no macOS é uma forma autossuficiente de cuidar das dependências, sem se preocupar se exitem ou não na máquina de instalação.
Frameworks .framework
são bundles, mas para bibliotecas. Um exemplo prático é o projeto Sparkle que é uma biblioteca que ajuda a checagem e atualização de arquivos de uma versão do software do bundle, assim que visitar o site vai se sentir familiarizado com a janela de exemplo.
Não só isso, mas também como um bundle é uma pasta, através dele é possível enviar imagens, icones, pacotes de idioma e outros tipos de arquivos essenciais para que a biblioteca funcione corretamente.
<app-name>.app/Contents/Info.plist
Muitas configurações/metadados em macOS são armazenadas e lidas de arquivos .plist
, nesse caso são metadados importantes para que o sistema reconheça sua pasta com extensão .app
como bundle corretamente. Um destes metadados é o idioma padrão a ser inicializado caso o seu sistema não possua idioma cadastrado no bundle.
São metadados:
- Nome que aparece na barra de menus
- Idioma padrão
- Caminho para o ícone, geralmente um arquivo
.icns
- Tipo do pacote
- Identificador da aplicação, algo como
<app-name>
oucom.flaverton.<app-name>
que também define algumas opções internas de armazenamento de configurações, encontradas em:- /User/<username>/Library/Application Support/com.flaverton.<app-name>
- /User/<username>/Library/Containers/com.flaverton.<app-name>
- /User/<username>/Library/Preferences/com.flaverton.<app-name>
Assim se entende um pouco da mágica feita por aplicativos como CleanMyMac de gerenciar cache de aplicativos e configurações.
<app-name>.app/Contents/Library
Nesta pasta podemos encontrar arquivos *.dynlib
(semelhantes ao papel de .dll
para Windows e .so
para Linux) e subpastas como:
- LaunchServices
- QuickLook
- LaunchItems
- […]
Nessas pastas são alocados partes do sistema para iniciar com o boot, daemons, configurações alocadas para o item de menu do acesso rápido, algumas destas conhecidas como helpers.
<app-name>.app/Contents/MacOS
Nesta pasta ficam executáveis do bundle, é comum o executável principal levar o mesmo nome do bundle (<app-name>.app
) sem a extensão app (<app-name>
). Com estrutura completa semelhante a:
<app-name>.app/Contents/MacOS/<app-name>
Por experiência com uma aplicação com código Qt e escrita em C++, todo executável que tem uma interface, deve ser posto nesta pasta, caso contrário, o executável apresenta um erro de nível do sistema operacional, dizendo que ele está tentando acessar recurso não autorizado.
A documentação menciona que executáveis ao estilo CLI podem ser colocados aqui, mas é comum encontrar em:
<app-name>.app/Contents/Resources
<app-name>.app/Contents/Share
Isso em bundles distribuídos fora da Mac App Store.
<app-name>.app/Contents/PkgInfo
Esse arquivo armazena 4 bytes definindo o tipo do seu bundle que geralmente é APPL
e 4 bytes de representação da aplicação, esses valores são uma referência aos valores apresentados no arquivo Info.plist nas propriedades CFBundlePackageType
e CFBundleSignature
.
<app-name>.app/Contents/Resources
Nesta pasta são armazenados diversos arquivos relacionados a aplicação como:
- Tradução (
.lproj
) - Icone (
.icns
) - Imagens (
*.png
) - Manual (
.pdf
)
<app-name>.app/Contents/PlugIns
Nesta pasta ficam armazenados as extensões do software .appex
(APPlication EXtension), que podem ser extensões para:
Algumas aplicações também armazenam seus plugins internos nesta pasta.
Assinatura do desenvolvedor no bundle
Todo desenvolvedor cadastrado na Apple, ganha credenciais que são inseridas no projeto do Xcode e quando gerado o bundle final são inseridas nele, isso faz com que o bundle seja validado como confiável.
Quando ele não possui essas credenciais e se tenta abrir o bundle no macOS, sempre é exibido uma tela pedindo para confirmar a abertura ou autorizar este software na central de segurança e privacidade.
Esses arquivos/pastas que descrevem a assinatura são:
- <app-name>.app/Contents/CodeResources
- <app-name>.app/Contents/_CodeSignature
Considerações finais
Acredito que muitas pessoas não pensariam que houvesse tanta semelhança com o tratamento de dependências com Windows, mas em partes essa abordagem torna muito mais simples a distribuíção e o controle destas aplicações por parte do desenvolvedor.
Isso é um post com bastante texto (história na introdução e explicação de pastas), mas que não se prende só a ele, caso queira desenvolver seu app e distribuir é importante que leia as referências officiais da Apple que orientar com mais clareza.
É importante também lembrar que o desenvolvimento de software é um aprendizado contínuo e mesmo lendo tudo, pode ser que falte algo, que tenha de ler mais e que os arquivos/pastas podem ou não estar nos bundles, sempre havendo a possibilidade de outros não listados, pois pode existir a necessidade individual de algum software de usar algo.
Referências
Oficiais da Apple
- About Code Signing - Code Signing Guide
- Additional Configuration Tips - Runtime Configuration Guidelines
- Bundle Programming Guide
- Software Delivery Legacy Guide - Specifying Install Operations
- Technical Note TN2206 - macOS Code Signing In Depth
- Understanding the Code Signature