Como crio um DMG de boa aparência para Mac OS X usando ferramentas de linha de comando?


212

Preciso criar um bom instalador para um aplicativo Mac. Quero que seja uma imagem de disco (DMG), com um tamanho predefinido, layout e imagem de fundo.

Eu preciso fazer isso programaticamente em um script, para ser integrado a um sistema de compilação existente (mais um sistema de pacote, na verdade, pois ele apenas cria instaladores. As compilações são feitas separadamente).

Já fiz a criação do DMG usando "hdiutil", o que ainda não descobri é como criar um layout de ícone e especificar um bitmap de plano de fundo.


1
Isso não é algo para Ask Different?
Lenar Hoyt

Respostas:


199

Depois de muita pesquisa, encontrei esta resposta e a coloco aqui como resposta para minha própria pergunta, para referência:

  1. Verifique se a opção "Ativar acesso para dispositivos auxiliares" está marcada em Preferências do sistema >> Acesso universal. É necessário que o AppleScript funcione. Pode ser necessário reiniciar após essa alteração (não funcionará de outra maneira no Mac OS X Server 10.4).

  2. Crie um DMG R / W. Deve ser maior do que o resultado será. Neste exemplo, a variável bash "size" contém o tamanho em Kb e o conteúdo da pasta na variável bash "source" será copiado no DMG:

    hdiutil create -srcfolder "${source}" -volname "${title}" -fs HFS+ \
          -fsargs "-c c=64,a=16,e=16" -format UDRW -size ${size}k pack.temp.dmg
    
  3. Monte a imagem do disco e armazene o nome do dispositivo (você pode usar o modo de suspensão por alguns segundos após esta operação):

    device=$(hdiutil attach -readwrite -noverify -noautoopen "pack.temp.dmg" | \
             egrep '^/dev/' | sed 1q | awk '{print $1}')
    
  4. Armazene a imagem de plano de fundo (no formato PNG) em uma pasta chamada ".background" no DMG e armazene seu nome na variável "backgroundPictureName".

  5. Use AppleScript para definir os estilos visuais (o nome de .app deve estar na variável de bash "applicationName", use variáveis ​​para as outras propriedades, conforme necessário):

    echo '
       tell application "Finder"
         tell disk "'${title}'"
               open
               set current view of container window to icon view
               set toolbar visible of container window to false
               set statusbar visible of container window to false
               set the bounds of container window to {400, 100, 885, 430}
               set theViewOptions to the icon view options of container window
               set arrangement of theViewOptions to not arranged
               set icon size of theViewOptions to 72
               set background picture of theViewOptions to file ".background:'${backgroundPictureName}'"
               make new alias file at container window to POSIX file "/Applications" with properties {name:"Applications"}
               set position of item "'${applicationName}'" of container window to {100, 100}
               set position of item "Applications" of container window to {375, 100}
               update without registering applications
               delay 5
               close
         end tell
       end tell
    ' | osascript
    
  6. Finalize o DMG definindo as permissões corretamente, compactando e liberando-o:

    chmod -Rf go-w /Volumes/"${title}"
    sync
    sync
    hdiutil detach ${device}
    hdiutil convert "/pack.temp.dmg" -format UDZO -imagekey zlib-level=9 -o "${finalDMGName}"
    rm -f /pack.temp.dmg 
    

No Snow Leopard, o AppleScript acima não define a posição do ícone corretamente - parece ser um bug do Snow Leopard. Uma solução alternativa é simplesmente chamar fechar / abrir depois de definir os ícones, ou seja:

..
set position of item "'${applicationName}'" of container window to {100, 100}
set position of item "Applications" of container window to {375, 100}
close
open

1
Excelente. Duas perguntas sobre isso: 1. Você recomenda sleepdepois de montar a imagem. Quão mais? Não é possível esperar deterministicamente pela conclusão do processo? Mesmo para o delay 5seu AppleScript. Sempre desconfio desses tempos de espera arbitrários, tendo tido uma experiência muito ruim com eles. 2. Na etapa 6, você liga syncduas vezes - por quê?
9289 Konrad Rudolph

Não encontrei nenhuma maneira de esperar deterministicamente pela conclusão do comando 'atualizar sem registrar aplicativos'. Não tenho certeza se o sono é necessário após o "hdiutil attach", você deverá verificar a documentação (man hdiutil). A sincronização deve ser necessária apenas para ser chamada uma vez; eu faço isso duas vezes fora do antigo hábito desde os bons e velhos dias do SunOS.
Ludvig A. Norin

1
Ele permite que o AppleScript simule a entrada do mouse e do teclado e várias outras coisas.
Ludvig A. Norin

1
Se o script travar até atingir o tempo limite na linha "atualizar sem registrar aplicativos", a etapa 1 não será concluída (ou será desfeita). Acabei de descobrir isso da maneira mais difícil.
Jay Maynard K5ZC

Acho que encontrei uma pequena falha - há uma ejeção no AppleScript, que é feita antes de você tentar executar o chmod. O chmod falhará porque o disco agora foi ejetado.
Trejkaz

56

Há um pequeno script do Bash chamado create-dmg que cria DMGs sofisticados com planos de fundo personalizados, posicionamento personalizado de ícones e nome do volume.

Eu o construí há muitos anos para a empresa que eu dirigia na época; sobrevive da contribuição de outras pessoas desde então e supostamente funciona bem.

Há também o node-appdmg, que parece um esforço mais moderno e ativo, baseado no Node.js. confira também.


7
O link para seu script bash está quebrado. Você pode colocá-lo em uma essência no github.com? Graças
dquimper

O create-dmg NÃO posiciona bem o ícone. Se você precisar desse recurso, não o use.
precisa saber é o seguinte

1
@ laike9m Faz mais de 5 anos desde que eu o toquei pela última vez. Eu realmente não considero mais os DMGs a melhor maneira de distribuir o software Mac. Portanto, ele sobrevive das contribuições de outras pessoas - espero que alguém descubra a questão dos ícones também.
Andrey Tarantsov

3
@ Zmey Bem como usuário, eu prefiro arquivos zip de longe. Pontos de bônus para copiar automaticamente /Applicationsse o aplicativo detectar que está sendo executado ~/Downloads. Veja também este artigo antigo de John Gruber .
Andrei Tarantsov


37

Não vá lá. Como desenvolvedor de longo prazo para Mac, posso garantir que nenhuma solução está realmente funcionando bem. Tentei tantas soluções, mas nem todas são boas. Penso que o problema é que a Apple não documenta realmente o formato de metadados para os dados necessários.

Eis como faço isso há muito tempo, com muito sucesso:

  1. Crie um novo DMG, gravável (!), Grande o suficiente para armazenar os arquivos binários e extras esperados, como leia-me (esparsos podem funcionar).

  2. Monte o DMG e forneça um layout manualmente no Finder ou com as ferramentas que mais lhe convierem (consulte o link FileStorm na parte inferior para obter uma boa ferramenta). A imagem de plano de fundo geralmente é uma imagem que colocamos em uma pasta oculta (".algo") no DMG. Coloque uma cópia do seu aplicativo lá (qualquer versão, mesmo a desatualizada serve). Copie outros arquivos (aliases, leia-me, etc.) que você deseja para lá. Novamente, as versões desatualizadas funcionarão perfeitamente. Verifique se os ícones têm os tamanhos e posições corretos (IOW, faça o layout do DMG da maneira que você deseja).

  3. Desmonte o DMG novamente, todas as configurações devem estar armazenadas agora.

  4. Escreva um script DMG de criação, que funcione da seguinte maneira:

    • Ele copia o DMG, para que o original nunca seja tocado novamente.
    • Monta a cópia.
    • Ele substitui todos os arquivos pelos mais atualizados (por exemplo, aplicativo mais recente após a compilação). Você pode simplesmente usar mv ou idem para isso na linha de comando. Observe que, quando você substitui um arquivo como esse, o ícone permanece o mesmo, a posição permanece a mesma, tudo, exceto o conteúdo do arquivo (ou diretório), permanece o mesmo (pelo menos com o mesmo nome, que geralmente usamos para essa tarefa) . Obviamente, você também pode substituir a imagem de fundo por outra (apenas verifique se ela tem as mesmas dimensões).
    • Depois de substituir os arquivos, faça o script desmontar a cópia do DMG novamente.
    • Por fim, chame o hdiutil para converter o DMG gravável em um DMG compactado (e não gravável).

Esse método pode não parecer ideal, mas confie em mim, ele funciona muito bem na prática. Você pode colocar o DMG original (modelo DMG) mesmo sob controle de versão (por exemplo, SVN); portanto, se você o alterar ou destruir acidentalmente, basta voltar para uma revisão onde ainda estava tudo bem. Você pode adicionar o modelo DMG ao seu projeto Xcode, juntamente com todos os outros arquivos que pertencem ao DMG (leia-me, arquivo URL, imagem de plano de fundo), todos sob controle de versão e, em seguida, criar um destino (por exemplo, destino externo chamado "Criar DMG") e execute o script DMG acima e adicione seu destino principal antigo como destino dependente. Você pode acessar arquivos na árvore do Xcode usando $ {SRCROOT} no script (é sempre a raiz de origem do seu produto) e pode acessar produtos de construção usando $ {BUILT_PRODUCTS_DIR} (é sempre o diretório em que o Xcode cria os resultados da construção) .

Resultado: Na verdade, o Xcode pode produzir o DMG no final da compilação. Um DMG que está pronto para lançamento. Não apenas você pode criar um DMG relase muito fácil dessa maneira, como também pode fazê-lo em um processo automatizado (em um servidor sem cabeça, se quiser), usando o xcodebuild na linha de comando (compilações noturnas automatizadas, por exemplo).

Em relação ao layout inicial do modelo, o FileStorm é uma boa ferramenta para fazê-lo. É comercial, mas muito poderoso e fácil de usar. A versão normal é inferior a US $ 20, por isso é realmente acessível. Talvez alguém possa automatizar o FileStorm para criar um DMG (por exemplo, via AppleScript), nunca tentei isso, mas depois de encontrar o DMG de modelo perfeito, é realmente fácil atualizá-lo para cada versão.


3
Já descartei a ideia de fazê-lo dessa maneira, por várias razões. Aqui estão dois deles: o conteúdo dos instaladores varia de acordo com o produto, e queremos confiar apenas no software instalado nas máquinas e scripts do pacote - uma rotina manual única e mínima para adicionar novos produtos.
Ludvig A. Norin

Este é o mesmo cenário que temos. Temos mais de uma dúzia de produtos; cada um tem um DMG totalmente diferente. Criar um DMG de modelo por produto é uma tarefa única e leva alguns minutos. E o que você quer dizer com "instalador"? PKG / MPKG instala pacotes?
Mecki 19/09/08

Não é o mesmo cenário. Nós adicionamos produtos frequentemente, com aviso prévio. A rotina manual mínima significa executar um script fornecendo um nome ao produto e alguns outros atributos. Existem outras razões além disso que nos fizeram tomar a decisão de não usar esse tipo de solução.
Ludvig A. Norin

Separamos o processo do pacote do processo de compilação, porque é feito por pessoas diferentes em momentos diferentes. O processo do pacote cria instaladores para Windows, Windows CE, Symbian, AIX, Linux e Solaris também.
Ludvig A. Norin

Você provavelmente está se referindo ao hdiutil, não ao hdutil.
Ivan Vučica 18/08/09

25

Atualizando esta pergunta, fornecendo esta resposta.

appdmgé um programa de linha de comando simples, fácil de usar e de código aberto que cria arquivos dmg a partir de uma simples especificação json. Dê uma olhada no leia-me no site oficial:

https://github.com/LinusU/node-appdmg

Exemplo rápido:

  1. Instalar appdmg

    npm install -g appdmg
    
  2. Escreva um arquivo json ( spec.json)

    {
      "title": "Test Title",
      "background": "background.png",
      "icon-size": 80,
      "contents": [
        { "x": 192, "y": 344, "type": "file", "path": "TestApp.app" },
        { "x": 448, "y": 344, "type": "link", "path": "/Applications" }
      ]
    }
    
  3. Rodar programa

    appdmg spec.json test.dmg
    

(aviso legal. Eu sou o criador do appdmg)


3
simples e eficaz. A única desvantagem é que ele requer npm para ser instalado
Jack James

@ Creator: você pode, por favor, avançar como essa GUI de uma oferta sem precisar arrastar e soltar? s.sudre.free.fr/Software/Packages/about.html

Se o arquivo .app não estiver na mesma pasta que o arquivo json, isso não funcionará, ocorrerá um erro "arquivo não encontrado" quando você especificar um caminho relativo em "caminho"
Joey

@ Joey, você poderia abrir um problema no repositório do Github se ele não funcionar?
Linus Unnebäck

@Joey: Não é necessário para ser na mesma pasta, tenho de fundo, app todos em caminho diferente, basta passar caminhos relativos como ../../../ABC
Anoop Vaidya

22

Para aqueles de vocês que estão interessados ​​neste tópico, devo mencionar como crio o DMG:

hdiutil create XXX.dmg -volname "YYY" -fs HFS+ -srcfolder "ZZZ"

Onde

XXX == disk image file name (duh!)
YYY == window title displayed when DMG is opened
ZZZ == Path to a folder containing the files that will be copied into the DMG

1
Isso é bom, mas não aborda o (imagem de fundo, o posicionamento de itens na pasta, etc.) real necessidade
Tom Bogle

criado DMG mas novamente eu tenho que executar o meu script (.sh) usando CMD eu preciso de sua executar automaticamente o sh depois de criar DMG
SANTOSH

13

Meu aplicativo, DropDMG , é uma maneira fácil de criar imagens de disco com imagens de plano de fundo, layouts de ícones, ícones de volume personalizados e contratos de licença de software. Ele pode ser controlado a partir de um sistema de compilação pela ferramenta de linha de comando "dropdmg" ou pelo AppleScript. Se desejar, os arquivos RTF de imagem e licença podem ser armazenados no seu sistema de controle de versão.


3
Minha equipe trabalha perfeitamente e automaticamente em um servidor de criação de Jenkins CI, completo com segundo plano, arraste para o alias de aplicativos. Corra, não ande, para o DropDMG para criar imagens de disco.
Paul Collins

Bom aplicativo, vou comprá-lo após o término do meu teste.
kilik52

Parece bom, mas parece não ter opção para redimensionar a janela.
avi

@avi O DropDMG dimensiona automaticamente a janela para a imagem de fundo que você definir (ou inserir nela, se desejar).
Michael Tsai

Ótimo, obrigado! É possível redimensionar sem a imagem de fundo?
avi


5

Para criar um DMG de boa aparência, agora você pode usar apenas fontes abertas bem escritas:


Talvez eles tenham mudado. Você pode usar create-dmg e node-appdmg. Eu estou usando create-dmg. É bom.
Anni S

@ PamelaCook-LightBeCorp Caso ainda esteja interessado. O link para o dmgbuild foi corrigido.
SubOptimal 27/09/17

4

Se você deseja definir o ícone de volume personalizado, use o comando abaixo

/*Add a drive icon*/
cp "/Volumes/customIcon.icns" "/Volumes/dmgName/.VolumeIcon.icns"  


/*SetFile -c icnC will change the creator of the file to icnC*/
SetFile -c icnC /<your path>/.VolumeIcon.icns

Agora crie dmg de leitura / gravação

/*to set custom icon attribute*/
SetFile -a C /Volumes/dmgName

Você pode explicar o que significa "seu caminho" aqui? Posso usar qualquer arquivo de ícones no disco e o SetFile copiá-lo ou precisaria usar um arquivo que esteja dentro de .dmg? Eu tenho apenas um Mac, então é difícil testar se as coisas funcionarão em outras máquinas.
Milan Babuškov 14/07/2014

"seu caminho" é o nome do DMG. (/ Volumes / dmgName)
Parag Bafna

Você deve copiar o arquivo icns. cp "/Volumes/customIcon.icns" "/Volumes/dmgName/.VolumeIcon.icns"
Parag Bafna

4

Finalmente consegui isso trabalhando no meu próprio projeto (que acontece no Xcode). A adição desses 3 scripts à sua fase de criação criará automaticamente uma Imagem de Disco para o seu produto, agradável e arrumada. Tudo o que você precisa fazer é criar seu projeto e o DMG estará esperando na sua pasta de produtos.

Script 1 (Criar imagem de disco temporário):

#!/bin/bash
#Create a R/W DMG

dir="$TEMP_FILES_DIR/disk"
dmg="$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.temp.dmg"

rm -rf "$dir"
mkdir "$dir"
cp -R "$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.app" "$dir"
ln -s "/Applications" "$dir/Applications"
mkdir "$dir/.background"
cp "$PROJECT_DIR/$PROJECT_NAME/some_image.png" "$dir/.background"
rm -f "$dmg"
hdiutil create "$dmg" -srcfolder "$dir" -volname "$PRODUCT_NAME" -format UDRW

#Mount the disk image, and store the device name
hdiutil attach "$dmg" -noverify -noautoopen -readwrite

Script 2 (Script de propriedades da janela Set):

#!/usr/bin/osascript
#get the dimensions of the main window using a bash script

set {width, height, scale} to words of (do shell script "system_profiler SPDisplaysDataType | awk '/Main Display: Yes/{found=1} /Resolution/{width=$2; height=$4} /Retina/{scale=($2 == \"Yes\" ? 2 : 1)} /^ {8}[^ ]+/{if(found) {exit}; scale=1} END{printf \"%d %d %d\\n\", width, height, scale}'")
set x to ((width / 2) / scale)
set y to ((height / 2) / scale)

#get the product name using a bash script
set {product_name} to words of (do shell script "printf \"%s\", $PRODUCT_NAME")
set background to alias ("Volumes:"&product_name&":.background:some_image.png")

tell application "Finder"
    tell disk product_name
        open
        set current view of container window to icon view
        set toolbar visible of container window to false
        set statusbar visible of container window to false
        set the bounds of container window to {x, y, (x + 479), (y + 383)}
        set theViewOptions to the icon view options of container window
        set arrangement of theViewOptions to not arranged
        set icon size of theViewOptions to 128
        set background picture of theViewOptions to background
        set position of item (product_name & ".app") of container window to {100, 225}
        set position of item "Applications" of container window to {375, 225}
        update without registering applications
        close
    end tell
end tell

A medida acima para a janela funciona para o meu projeto, especificamente devido ao tamanho da minha imagem de plano de fundo e resolução do ícone; pode ser necessário modificar esses valores para seu próprio projeto.

Script 3 (Criar script final da imagem do disco):

#!/bin/bash
dir="$TEMP_FILES_DIR/disk"
cp "$PROJECT_DIR/$PROJECT_NAME/some_other_image.png" "$dir/"

#unmount the temp image file, then convert it to final image file
sync
sync
hdiutil detach /Volumes/$PRODUCT_NAME
rm -f "$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.dmg"
hdiutil convert "$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.temp.dmg" -format UDZO -imagekey zlib-level=9 -o "$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.dmg"
rm -f "$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.temp.dmg"

#Change the icon of the image file
sips -i "$dir/some_other_image.png"
DeRez -only icns "$dir/some_other_image.png" > "$dir/tmpicns.rsrc"
Rez -append "$dir/tmpicns.rsrc" -o "$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.dmg"
SetFile -a C "$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.dmg"

rm -rf "$dir"

Verifique se os arquivos de imagem que você está usando estão no diretório $ PROJECT_DIR / $ PROJECT_NAME /!


Ele está criando uma imagem de disco em branco no meu projeto. Alguma sugestão.
ManiaChamp

A parte do script de shell funciona apenas, mas como posso adicionar o script da Apple com o Shell Script no RunScript, na fase Fase de compilação, está exibindo erro, todas as instruções do script da apple são símbolo não encontrado.
ManiaChamp 28/01

2

Os arquivos .DS_Store armazenam as configurações do Windows no Mac. As configurações do Windows incluem o layout dos ícones, o plano de fundo da janela, o tamanho da janela, etc. O arquivo .DS_Store é necessário na criação da janela para imagens montadas para preservar a organização dos arquivos e o plano de fundo da janela.

Depois de criar o arquivo .DS_Store, você pode copiá-lo no seu instalador criado (DMG).


Mas primeiro adicione uma imagem de plano de fundo ao dmg, nomeando-a como .background.png e, em seguida, use Cmd-J para adicionar essa imagem (!) Ao plano de fundo.
Gijs

2

Eu também preciso usar a abordagem de linha de comando para fazer a criação de pacotes e dmg "programaticamente em um script". A melhor resposta que encontrei até agora é da estrutura de construção do release do projeto Adium (consulte R1). Há um script personalizado (AdiumApplescriptRunner) para permitir que você evite a interação da OSI WindowsServer GUI. A abordagem "osascript applescript.scpt" exige que você efetue login como construtor e execute a criação do dmg a partir de uma sessão da linha de comandos vt100.

O sistema de gerenciamento de pacotes OSX não é tão avançado em comparação com outros Unixen, que podem executar essa tarefa de maneira fácil e sistemática.

R1: http://hg.adium.im/adium-1.4/file/00d944a3ef16/Release


2

Acabei de escrever um novo utilitário de linha de comando (amigável) para fazer isso. Ele não depende do Finder / AppleScript ou de qualquer uma das APIs do Alias ​​Manager (obsoleta), e é fácil de configurar e usar.

De qualquer forma, qualquer pessoa interessada pode encontrá-lo no PyPi ; a documentação está disponível em Leia os documentos .


1

Essas respostas são muito complicadas e os tempos mudaram. O seguinte funciona com a versão 10.9, as permissões estão corretas e parecem boas.

Crie um DMG somente leitura a partir de um diretório

#!/bin/sh
# create_dmg Frobulator Frobulator.dmg path/to/frobulator/dir [ 'Your Code Sign Identity' ]
set -e

VOLNAME="$1"
DMG="$2"
SRC_DIR="$3"
CODESIGN_IDENTITY="$4"

hdiutil create -srcfolder "$SRC_DIR" \
  -volname "$VOLNAME" \
  -fs HFS+ -fsargs "-c c=64,a=16,e=16" \
  -format UDZO -imagekey zlib-level=9 "$DMG"

if [ -n "$CODESIGN_IDENTITY" ]; then
  codesign -s "$CODESIGN_IDENTITY" -v "$DMG"
fi

Crie DMG somente leitura com um ícone (tipo .icns)

#!/bin/sh
# create_dmg_with_icon Frobulator Frobulator.dmg path/to/frobulator/dir path/to/someicon.icns [ 'Your Code Sign Identity' ]
set -e
VOLNAME="$1"
DMG="$2"
SRC_DIR="$3"
ICON_FILE="$4"
CODESIGN_IDENTITY="$5"

TMP_DMG="$(mktemp -u -t XXXXXXX)"
trap 'RESULT=$?; rm -f "$TMP_DMG"; exit $RESULT' INT QUIT TERM EXIT
hdiutil create -srcfolder "$SRC_DIR" -volname "$VOLNAME" -fs HFS+ \
               -fsargs "-c c=64,a=16,e=16" -format UDRW "$TMP_DMG"
TMP_DMG="${TMP_DMG}.dmg" # because OSX appends .dmg
DEVICE="$(hdiutil attach -readwrite -noautoopen "$TMP_DMG" | awk 'NR==1{print$1}')"
VOLUME="$(mount | grep "$DEVICE" | sed 's/^[^ ]* on //;s/ ([^)]*)$//')"
# start of DMG changes
cp "$ICON_FILE" "$VOLUME/.VolumeIcon.icns"
SetFile -c icnC "$VOLUME/.VolumeIcon.icns"
SetFile -a C "$VOLUME"
# end of DMG changes
hdiutil detach "$DEVICE"
hdiutil convert "$TMP_DMG" -format UDZO -imagekey zlib-level=9 -o "$DMG"
if [ -n "$CODESIGN_IDENTITY" ]; then
  codesign -s "$CODESIGN_IDENTITY" -v "$DMG"
fi

Se algo mais precisar acontecer, o mais fácil é fazer uma cópia temporária do SRC_DIR e aplicar as alterações antes de criar um DMG.


Essa resposta não adiciona nada ao que foi escrito anteriormente e também não responde à pergunta original (não se trata apenas de colocar um ícone no dmg ou de como assiná-lo; a questão é de adicionar gráficos e ícones posicionados programaticamente para um dmg).
Ludvig A. Norin
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.