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> ou com.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

Gerais