Skip to content

Commit

Permalink
fix: Linux build fails at icon conversion electron-userland#239
Browse files Browse the repository at this point in the history
  • Loading branch information
develar committed Mar 16, 2016
1 parent c778e2b commit 6ff6787
Show file tree
Hide file tree
Showing 24 changed files with 126 additions and 701 deletions.
1 change: 1 addition & 0 deletions .idea/dictionaries/develar.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .travis.yml
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ addons:

before_install:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install gnu-tar dpkg libicns; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install gnu-tar dpkg libicns homebrew/science/vips; fi
- gem install --no-rdoc --no-ri fpm

install:
Expand Down
37 changes: 19 additions & 18 deletions README.md
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,23 @@ Why the two package.json structure is ideal and how it solves a lot of issues

# Configuration
## In short
1. Ensure that required fields are specified in the application `package.json`:
1. Ensure that required fields are specified in the application `package.json`:

Standard `name`, `description`, `version` and `author`.
Standard `name`, `description`, `version` and `author`.

Custom `build` field must be specified:
```json
"build": {
"app-bundle-id": "your.id",
"app-category-type": "your.app.category.type",
"iconUrl": "(windows only) A URL to an ICO file to use as the application icon, see details below"
}
```
This object will be used as a source of [electron-packager](https://www.npmjs.com/package/electron-packager#packageropts-callback) options. You can specify any other options here.
Custom `build` field must be specified:
```json
"build": {
"app-bundle-id": "your.id",
"app-category-type": "your.app.category.type",
"iconUrl": "(windows only) A URL to an ICO file to use as the application icon, see details below"
}
```
This object will be used as a source of [electron-packager](https://www.npmjs.com/package/electron-packager#packageropts-callback) options. You can specify any other options here.

2. Create directory `build` in the root of the project and put your `background.png` (OS X DMG background), `icon.icns` (OS X app icon) and `icon.ico` (Windows app icon).
Linux icon set will be generated automatically on the fly from the OS X `icns` file.

Linux icon set will be generated automatically on the fly from the OS X `icns` file (or you can put them into the `build/icons` directory — filename must contains size (e.g. `32x32.png`)).

3. Add [scripts](https://docs.npmjs.com/cli/run-script) to the development `package.json`:
```json
Expand All @@ -64,11 +65,6 @@ Why the two package.json structure is ideal and how it solves a lot of issues

4. Install [required system packages](./docs/multi-platform-build.md).

## iconUrl
Please note — [local icon file url is not accepted](https://github.com/atom/grunt-electron-installer/issues/73), must be https/http.
* If you don't plan to build windows installer, you can omit it.
* If your project repository is public on GitHub, it will be `https://raw.githubusercontent.com/${info.user}/${info.project}/master/build/icon.ico` by default.

## Distributable Format Configuration
In the development `package.json` custom `build` field can be specified to customize distributable format:
```json
Expand Down Expand Up @@ -100,7 +96,12 @@ In the development `package.json` custom `build` field can be specified to custo
As you can see, you need to customize OS X options only if you want to provide custom `x, y`.
Don't customize paths to background and icon, — just follow conventions (if you don't want to use `build` as directory of resources — please create issue to ask ability to customize it).

See [OS X options](https://www.npmjs.com/package/appdmg#json-specification) and [Windows options](https://github.com/electronjs/windows-installer#usage).
See [OS X options](https://www.npmjs.com/package/appdmg#json-specification) and [Windows options](https://github.com/electronjs/windows-installer#usage). Here documented only `electron-builder` specific options:

| Name | Description
| --- | ---
| <a name="iconUrl"></a>iconUrl<br/> | <p>*windows-only.* A URL to an ICO file to use as the application icon (displayed in Control Panel > Programs and Features). Defaults to the Atom icon.</p><p>Please note — [local icon file url is not accepted](https://github.com/atom/grunt-electron-installer/issues/73), must be https/http.</p><ul><li>If you don't plan to build windows installer, you can omit it.</li><li>If your project repository is public on GitHub, it will be `https://raw.githubusercontent.com/${info.user}/${info.project}/master/build/icon.ico` by default.</li></ul>
| <a name="extraResources"></a>extraResources | <p>A [glob expression](https://www.npmjs.com/package/glob#glob-primer), when specified, copy the file or directory with matching names directly into the app's directory (`Contents/Resources` for OS X).</p><p>You can use `${os}` (expanded to osx, linux, win according to current platform) and `${arch}` in the pattern.</p><p>If directory matched, all contents are copied. So, you can just specify `foo` to copy `<project_dir>/foo` directory.</p><p>May be specified in the platform options (i.e. in the `build.osx`).

# Auto Update
`electron-builder` produces all required artifacts:
Expand Down
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ cache:
- '%USERPROFILE%\.electron'

init:
- git config --global core.autocrlf input
- git config --global core.autocrlf false

install:
- choco install nsis -y
Expand Down
4 changes: 2 additions & 2 deletions docs/multi-platform-build.md
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ brew install Caskroom/cask/xquartz wine mono

To build app in distributable format for Linux on OS X:
```
brew install ruby gnu-tar libicns imagemagick graphicsmagick
brew install ruby gnu-tar libicns homebrew/science/vips
gem install fpm
```

## Linux
To build app in distributable format for Linux:
```
sudo apt-get install icnsutils graphicsmagick
sudo apt-get install icnsutils
```

## Windows
Expand Down
8 changes: 4 additions & 4 deletions package.json
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,10 @@
"bluebird": "^3.3.4",
"command-line-args": "^2.1.6",
"electron-packager-tf": "^5.2.3",
"electron-winstaller-fixed": "^2.0.5-beta.7",
"fs-extra": "^0.26.5",
"electron-winstaller-fixed": "^2.0.6-beta.1",
"fs-extra": "^0.26.6",
"fs-extra-p": "^0.1.0",
"globby": "^4.0.0",
"gm": "^1.21.1",
"hosted-git-info": "^2.1.4",
"lodash.template": "^4.2.2",
"meow": "^3.7.0",
Expand All @@ -69,7 +68,8 @@
"tmp": "0.0.28"
},
"optionalDependencies": {
"appdmg": "^0.3.6"
"appdmg": "^0.3.6",
"sharp": "^0.13.1"
},
"devDependencies": {
"ava-tf": "^0.12.4-beta.6",
Expand Down
88 changes: 60 additions & 28 deletions src/linuxPackager.ts
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { PlatformPackager, BuildInfo } from "./platformPackager"
import { Platform } from "./metadata"
import { dir as _tpmDir, TmpOptions } from "tmp"
import { exec, log } from "./util"
import { State as Gm } from "gm"
import { outputFile, readFile, stat } from "fs-extra-p"
import { outputFile, readFile, readdir } from "fs-extra-p"
import Sharp = require("sharp")
const template = require("lodash.template")

//noinspection JSUnusedLocalSymbols
Expand Down Expand Up @@ -67,45 +67,77 @@ Icon=${this.metadata.name}
return [`${tempFile}=/usr/share/applications/${this.appName}.desktop`]
}

// must be name without spaces and other special characters, but not product name used
private async computeDesktopIconPath(tempDir: string): Promise<Array<string>> {
const outputs = await exec("icns2png", ["-x", "-o", tempDir, path.join(this.buildResourcesDir, "icon.icns")])
log(outputs[0].toString())
if (!outputs[0].toString().includes("ih32")) {
log("48x48 is not found in the icns, 128x128 or 256x256 will be resized")

const gm = require("gm")

// icns doesn't contain required 48x48, use gm to resize
function resize(imagePath: string, size: number): BluebirdPromise<any> {
return new BluebirdPromise((resolve, reject) => {
(<Gm>gm(imagePath))
.resize(size, size)
.write(path.join(tempDir, `icon_${size}x${size}x32.png`), error => error == null ? resolve() : reject(error))
})
try {
const mappings: Array<string> = []
const pngIconsDir = path.join(this.buildResourcesDir, "icons")
for (let file of (await readdir(pngIconsDir))) {
if (file.endsWith(".png") || file.endsWith(".PNG")) {
// If parseInt encounters a character that is not a numeral in the specified radix,
// it returns the integer value parsed up to that point
try {
const size = parseInt(file, 10)
if (size > 0) {
mappings.push(`${pngIconsDir}/${file}=/usr/share/icons/hicolor/${size}x${size}/apps/${this.metadata.name}.png`)
}
}
catch (e) {
console.error(e)
}
}
}

let imagePath = path.join(tempDir, "icon_128x128x32.png")
try {
await stat(imagePath)
}
catch (e) {
imagePath = path.join(tempDir, "icon_256x256x32.png")
// 128 should be in any case
await resize(imagePath, 128)
}
await resize(imagePath, 48)
return mappings
}
catch (e) {
return this.createFromIcns(tempDir)
}
}

private async createFromIcns(tempDir: string): Promise<Array<string>> {
const outputs = await exec("icns2png", ["-x", "-o", tempDir, path.join(this.buildResourcesDir, "icon.icns")])
const output = outputs[0].toString()
log(output)

const sharp: Sharp = require("sharp")

const imagePath = path.join(tempDir, "icon_256x256x32.png")

function resize(size: number): BluebirdPromise<any> {
return new BluebirdPromise((resolve, reject) => {
sharp(imagePath)
.resize(size, size)
.toFile(path.join(tempDir, `icon_${size}x${size}x32.png`), error => error == null ? resolve() : reject(error))
})
}

const promises: Array<Promise<any>> = [resize(24), resize(96)]
if (!output.includes("ih32")) {
promises.push(resize(48))
}
if (!output.toString().includes("icp6")) {
promises.push(resize(64))
}
if (!output.includes("it32")) {
promises.push(resize(128))
}

await BluebirdPromise.all(promises)

const name = this.metadata.name
const appName = this.metadata.name

function createMapping(size: string) {
return `${tempDir}/icon_${size}x${size}x32.png=/usr/share/icons/hicolor/${size}x${size}/apps/${name}.png`
return `${tempDir}/icon_${size}x${size}x32.png=/usr/share/icons/hicolor/${size}x${size}/apps/${appName}.png`
}

return [
createMapping("16"),
createMapping("24"),
createMapping("32"),
createMapping("48"),
createMapping("64"),
createMapping("96"),
createMapping("128"),
createMapping("256"),
createMapping("512"),
Expand Down
4 changes: 3 additions & 1 deletion src/metadata.ts
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export interface AppMetadata extends Metadata {
readonly version: string

/** The application name */
/**
* The application name
**/
readonly name: string

/**
Expand Down
2 changes: 1 addition & 1 deletion test/README.md
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ brew install Caskroom/cask/xquartz wine mono
Do not use OS X bundled Ruby. Install using `brew`.

```
brew install ruby gnu-tar dpkg libicns
brew install ruby gnu-tar dpkg libicns homebrew/science/vips
gem install fpm
```

Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/no-author-email/package.json
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"description": "Test Application",
"author": "Foo Bar",
"devDependencies": {
"electron-prebuilt": "^0.37.1"
"electron-prebuilt": "^0.37.2"
},
"build": {
"app-bundle-id": "your.id",
Expand Down
Binary file modified test/fixtures/test-app-one/build/background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion test/fixtures/test-app-one/package.json
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
},
"author": "Foo Bar <[email protected]>",
"devDependencies": {
"electron-prebuilt": "^0.37.1"
"electron-prebuilt": "^0.37.2"
},
"build": {
"app-bundle-id": "your.id",
Expand Down
Binary file modified test/fixtures/test-app/build/background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion test/fixtures/test-app/package.json
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
"start": "electron ."
},
"devDependencies": {
"electron-prebuilt": "^0.37.1"
"electron-prebuilt": "^0.37.2"
}
}
6 changes: 3 additions & 3 deletions test/src/BuildTest.ts
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ test.ifOsx("mac: one-package.json", async () => {
})

test("custom app dir", async () => {
await assertPack("test-app-one", allPlatformsAndCurrentArch(), true, (projectDir) => {
await assertPack("test-app-one", allPlatformsAndCurrentArch(), (projectDir) => {
return BluebirdPromise.all([
modifyPackageJson(projectDir, data => {
data.directories = {
Expand All @@ -36,7 +36,7 @@ test("custom app dir", async () => {
})

test("productName with space", async () => {
await assertPack("test-app-one", allPlatformsAndCurrentArch(), true, (projectDir) => {
await assertPack("test-app-one", allPlatformsAndCurrentArch(), (projectDir) => {
return modifyPackageJson(projectDir, data => {
data.productName = "Test App"
})
Expand All @@ -51,7 +51,7 @@ test("copy extra resource", async () => {
platform: [platform],
arch: process.arch,
dist: false
}, true, (projectDir) => {
}, (projectDir) => {
return BluebirdPromise.all([
modifyPackageJson(projectDir, data => {
if (data.build == null) {
Expand Down
11 changes: 10 additions & 1 deletion test/src/helpers/expectedContents.ts
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,29 @@ export const expectedLinuxContents = [
"/usr/share/icons/hicolor/",
"/usr/share/icons/hicolor/128x128/",
"/usr/share/icons/hicolor/16x16/",
"/usr/share/icons/hicolor/24x24/",
"/usr/share/icons/hicolor/256x256/",
"/usr/share/icons/hicolor/32x32/",
"/usr/share/icons/hicolor/48x48/",
"/usr/share/icons/hicolor/512x512/",
"/usr/share/icons/hicolor/64x64/",
"/usr/share/icons/hicolor/96x96/",
"/usr/share/icons/hicolor/128x128/apps/",
"/usr/share/icons/hicolor/128x128/apps/TestApp.png",
"/usr/share/icons/hicolor/16x16/apps/",
"/usr/share/icons/hicolor/16x16/apps/TestApp.png",
"/usr/share/icons/hicolor/24x24/apps/",
"/usr/share/icons/hicolor/24x24/apps/TestApp.png",
"/usr/share/icons/hicolor/256x256/apps/",
"/usr/share/icons/hicolor/256x256/apps/TestApp.png",
"/usr/share/icons/hicolor/32x32/apps/",
"/usr/share/icons/hicolor/32x32/apps/TestApp.png",
"/usr/share/icons/hicolor/48x48/apps/",
"/usr/share/icons/hicolor/48x48/apps/TestApp.png",
"/usr/share/icons/hicolor/512x512/apps/",
"/usr/share/icons/hicolor/512x512/apps/TestApp.png"
"/usr/share/icons/hicolor/512x512/apps/TestApp.png",
"/usr/share/icons/hicolor/64x64/apps/",
"/usr/share/icons/hicolor/64x64/apps/TestApp.png",
"/usr/share/icons/hicolor/96x96/apps/",
"/usr/share/icons/hicolor/96x96/apps/TestApp.png"
]
11 changes: 5 additions & 6 deletions test/src/helpers/packTester.ts
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ let tmpDirCounter = 0

export async function assertPack(fixtureName: string,
packagerOptions: PackagerOptions,
useTempDir?: boolean,
tempDirCreated?: (projectDir: string) => Promise<any>,
packed?: (projectDir: string) => Promise<any>) {
const useTempDir = tempDirCreated != null || (packagerOptions != null && packagerOptions.target != null)

let projectDir = path.join(__dirname, "..", "..", "fixtures", fixtureName)
// const isDoNotUseTempDir = platform === "darwin"
const customTmpDir = process.env.TEST_APP_TMP_DIR
Expand Down Expand Up @@ -110,16 +111,14 @@ async function packAndCheck(projectDir: string, packagerOptions: PackagerOptions
return it.replace(new RegExp("/opt/TestApp/", "g"), `/opt/${productName}/`)
}
})
// let normalizedAppName = getProductName(packager.metadata).toLowerCase().replace(/ /g, '-')
// expectedContents[expectedContents.indexOf("/usr/share/doc/testapp/")] = "/usr/share/doc/" + normalizedAppName + "/"
// expectedContents[expectedContents.indexOf("/usr/share/doc/testapp/changelog.Debian.gz")] = "/usr/share/doc/" + normalizedAppName + "/changelog.Debian.gz"

// console.log(JSON.stringify(await getContents(projectDir + "/dist/TestApp-1.0.0-amd64.deb", productName), null, 2))
// console.log(JSON.stringify(await getContents(projectDir + "/dist/TestApp-1.0.0-i386.deb", productName), null, 2))

assertThat(await getContents(projectDir + "/dist/TestApp-1.0.0-amd64.deb", productName)).deepEqual(expectedContents)
if (packagerOptions == null || packagerOptions.arch === null || packagerOptions.arch === "ia32") {
assertThat(await getContents(projectDir + "/dist/TestApp-1.0.0-i386.deb", productName)).deepEqual(expectedContents)
}
// console.log(JSON.stringify(await getContents(projectDir + "/dist/TestApp-1.0.0-amd64.deb"), null, 2))
// console.log(JSON.stringify(await getContents(projectDir + "/dist/TestApp-1.0.0-i386.deb"), null, 2))
}
else if (expandedPlatforms.includes("win32") && (packagerOptions == null || packagerOptions.target == null)) {
await checkWindowsResult(packagerOptions, artifacts.get(Platform.WINDOWS))
Expand Down
2 changes: 1 addition & 1 deletion test/src/helpers/runTests.ts
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const rootDir = path.join(__dirname, "..", "..", "..")
const testPackageDir = path.join(require("os").tmpdir(), "electron_builder_published")
const testNodeModules = path.join(testPackageDir, "node_modules")

const electronVersion = "0.37.1"
const electronVersion = "0.37.2"

BluebirdPromise.all([
deleteOldElectronVersion(),
Expand Down
Loading

0 comments on commit 6ff6787

Please sign in to comment.