diff --git a/docs/build-instructions/linux.md b/docs/build-instructions/linux.md index be3e02846..2fc46c0b2 100644 --- a/docs/build-instructions/linux.md +++ b/docs/build-instructions/linux.md @@ -17,7 +17,7 @@ Ubuntu LTS 12.04 64-bit is the recommended platform. ### Ubuntu / Debian * `sudo apt-get install build-essential git libgnome-keyring-dev fakeroot` -* Instructions for [Node.js](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#ubuntu-mint-elementary-os). +* Instructions for [Node.js](https://github.com/nodejs/node-v0.x-archive/wiki/Installing-Node.js-via-package-manager#debian-and-ubuntu-based-linux-distributions). * Make sure the command `node` is available after Node.js installation (some systems install it as `nodejs`). * Use `which node` to check if it is available. * Use `sudo update-alternatives --install /usr/bin/node node /usr/bin/nodejs 10` to update it. @@ -25,7 +25,7 @@ Ubuntu LTS 12.04 64-bit is the recommended platform. ### Fedora / CentOS / RHEL * `sudo dnf --assumeyes install make gcc gcc-c++ glibc-devel git-core libgnome-keyring-devel rpmdevtools` -* Instructions for [Node.js](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#fedora). +* Instructions for [Node.js](https://github.com/nodejs/node-v0.x-archive/wiki/Installing-Node.js-via-package-manager#enterprise-linux-and-fedora). ### Arch diff --git a/package.json b/package.json index fa3e6e273..5aecf66ce 100644 --- a/package.json +++ b/package.json @@ -69,10 +69,10 @@ "atom-light-ui": "0.43.0", "base16-tomorrow-dark-theme": "0.27.0", "base16-tomorrow-light-theme": "0.9.0", - "one-dark-ui": "1.0.3", + "one-dark-ui": "1.0.4", "one-dark-syntax": "1.1.0", "one-light-syntax": "1.1.0", - "one-light-ui": "1.0.3", + "one-light-ui": "1.0.4", "solarized-dark-syntax": "0.38.1", "solarized-light-syntax": "0.22.1", "about": "1.1.0", @@ -85,7 +85,7 @@ "autoflow": "0.25.0", "autosave": "0.22.0", "background-tips": "0.26.0", - "bookmarks": "0.36.0", + "bookmarks": "0.37.0", "bracket-matcher": "0.76.0", "command-palette": "0.36.0", "deprecation-cop": "0.54.0", @@ -94,11 +94,11 @@ "exception-reporting": "0.36.0", "find-and-replace": "0.180.0", "fuzzy-finder": "0.88.0", - "git-diff": "0.55.0", + "git-diff": "0.56.0", "go-to-line": "0.30.0", "grammar-selector": "0.47.0", "image-view": "0.54.0", - "incompatible-packages": "0.24.1", + "incompatible-packages": "0.25.0", "keybinding-resolver": "0.33.0", "line-ending-selector": "0.0.5", "link": "0.30.0", @@ -116,7 +116,7 @@ "symbols-view": "0.104.0", "tabs": "0.84.0", "timecop": "0.31.0", - "tree-view": "0.186.0", + "tree-view": "0.187.0", "update-package-dependencies": "0.10.0", "welcome": "0.30.0", "whitespace": "0.31.0", diff --git a/spec/package-spec.coffee b/spec/package-spec.coffee index 9ea000ab9..ef5f5a4c3 100644 --- a/spec/package-spec.coffee +++ b/spec/package-spec.coffee @@ -7,6 +7,10 @@ describe "Package", -> describe "when the package contains incompatible native modules", -> beforeEach -> spyOn(atom, 'inDevMode').andReturn(false) + items = {} + spyOn(global.localStorage, 'setItem').andCallFake (key, item) -> items[key] = item; undefined + spyOn(global.localStorage, 'getItem').andCallFake (key) -> items[key] ? null + spyOn(global.localStorage, 'removeItem').andCallFake (key) -> delete items[key]; undefined it "does not activate it", -> packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-incompatible-native-module') @@ -17,14 +21,6 @@ describe "Package", -> it "caches the incompatible native modules in local storage", -> packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-incompatible-native-module') - cacheKey = null - cacheItem = null - - spyOn(global.localStorage, 'setItem').andCallFake (key, item) -> - cacheKey = key - cacheItem = item - spyOn(global.localStorage, 'getItem').andCallFake (key) -> - return cacheItem if cacheKey is key expect(new Package(packagePath).isCompatible()).toBe false expect(global.localStorage.getItem.callCount).toBe 1 @@ -34,6 +30,71 @@ describe "Package", -> expect(global.localStorage.getItem.callCount).toBe 2 expect(global.localStorage.setItem.callCount).toBe 1 + describe "::rebuild()", -> + beforeEach -> + spyOn(atom, 'inDevMode').andReturn(false) + items = {} + spyOn(global.localStorage, 'setItem').andCallFake (key, item) -> items[key] = item; undefined + spyOn(global.localStorage, 'getItem').andCallFake (key) -> items[key] ? null + spyOn(global.localStorage, 'removeItem').andCallFake (key) -> delete items[key]; undefined + + it "returns a promise resolving to the results of `apm rebuild`", -> + packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-index') + pack = new Package(packagePath) + rebuildCallbacks = [] + spyOn(pack, 'runRebuildProcess').andCallFake ((callback) -> rebuildCallbacks.push(callback)) + + promise = pack.rebuild() + rebuildCallbacks[0]({code: 0, stdout: 'stdout output', stderr: 'stderr output'}) + + waitsFor (done) -> + promise.then (result) -> + expect(result).toEqual {code: 0, stdout: 'stdout output', stderr: 'stderr output'} + done() + + it "persists build failures in local storage", -> + packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-index') + pack = new Package(packagePath) + + expect(pack.isCompatible()).toBe true + expect(pack.getBuildFailureOutput()).toBeNull() + + rebuildCallbacks = [] + spyOn(pack, 'runRebuildProcess').andCallFake ((callback) -> rebuildCallbacks.push(callback)) + + pack.rebuild() + rebuildCallbacks[0]({code: 13, stderr: 'It is broken'}) + + expect(pack.getBuildFailureOutput()).toBe 'It is broken' + expect(pack.getIncompatibleNativeModules()).toEqual [] + expect(pack.isCompatible()).toBe false + + # A different package instance has the same failure output (simulates reload) + pack2 = new Package(packagePath) + expect(pack2.getBuildFailureOutput()).toBe 'It is broken' + expect(pack2.isCompatible()).toBe false + + # Clears the build failure after a successful build + pack.rebuild() + rebuildCallbacks[1]({code: 0, stdout: 'It worked'}) + + expect(pack.getBuildFailureOutput()).toBeNull() + expect(pack2.getBuildFailureOutput()).toBeNull() + + it "sets cached incompatible modules to an empty array when the rebuild completes (there may be a build error, but rebuilding *deletes* native modules)", -> + packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-incompatible-native-module') + pack = new Package(packagePath) + + expect(pack.getIncompatibleNativeModules().length).toBeGreaterThan(0) + + rebuildCallbacks = [] + spyOn(pack, 'runRebuildProcess').andCallFake ((callback) -> rebuildCallbacks.push(callback)) + + pack.rebuild() + expect(pack.getIncompatibleNativeModules().length).toBeGreaterThan(0) + rebuildCallbacks[0]({code: 0, stdout: 'It worked'}) + expect(pack.getIncompatibleNativeModules().length).toBe(0) + describe "theme", -> theme = null diff --git a/src/package.coffee b/src/package.coffee index 25bf3a038..f3f5cf4a2 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -11,6 +11,7 @@ Q = require 'q' ModuleCache = require './module-cache' ScopedProperties = require './scoped-properties' +BufferedProcess = require './buffered-process' packagesCache = require('../package.json')?._atomPackages ? {} @@ -603,6 +604,70 @@ class Package traversePath(path.join(@path, 'node_modules')) nativeModulePaths + ### + Section: Native Module Compatibility + ### + + # Extended: Are all native modules depended on by this package correctly + # compiled against the current version of Atom? + # + # Incompatible packages cannot be activated. + # + # Returns a {Boolean}, true if compatible, false if incompatible. + isCompatible: -> + return @compatible if @compatible? + + if @path.indexOf(path.join(atom.packages.resourcePath, 'node_modules') + path.sep) is 0 + # Bundled packages are always considered compatible + @compatible = true + else if @getMainModulePath() + @incompatibleModules = @getIncompatibleNativeModules() + @compatible = @incompatibleModules.length is 0 and not @getBuildFailureOutput()? + else + @compatible = true + + # Extended: Rebuild native modules in this package's dependencies for the + # current version of Atom. + # + # Returns a {Promise} that resolves with an object containing `code`, + # `stdout`, and `stderr` properties based on the results of running + # `apm rebuild` on the package. + rebuild: -> + new Promise (resolve) => + @runRebuildProcess (result) => + if result.code is 0 + global.localStorage.removeItem(@getBuildFailureOutputStorageKey()) + else + @compatible = false + global.localStorage.setItem(@getBuildFailureOutputStorageKey(), result.stderr) + global.localStorage.setItem(@getIncompatibleNativeModulesStorageKey(), '[]') + resolve(result) + + # Extended: If a previous rebuild failed, get the contents of stderr. + # + # Returns a {String} or null if no previous build failure occurred. + getBuildFailureOutput: -> + global.localStorage.getItem(@getBuildFailureOutputStorageKey()) + + runRebuildProcess: (callback) -> + stderr = '' + stdout = '' + new BufferedProcess({ + command: atom.packages.getApmPath() + args: ['rebuild', '--no-color'] + options: {cwd: @path} + stderr: (output) -> stderr += output + stdout: (output) -> stdout += output + exit: (code) -> callback({code, stdout, stderr}) + }) + + getBuildFailureOutputStorageKey: -> + "installed-packages:#{@name}:#{@metadata.version}:build-error" + + getIncompatibleNativeModulesStorageKey: -> + electronVersion = process.versions['electron'] ? process.versions['atom-shell'] + "installed-packages:#{@name}:#{@metadata.version}:electron-#{electronVersion}:incompatible-native-modules" + # Get the incompatible native modules that this package depends on. # This recurses through all dependencies and requires all modules that # contain a `.node` file. @@ -610,11 +675,10 @@ class Package # This information is cached in local storage on a per package/version basis # to minimize the impact on startup time. getIncompatibleNativeModules: -> - localStorageKey = "installed-packages:#{@name}:#{@metadata.version}" unless atom.inDevMode() try - {incompatibleNativeModules} = JSON.parse(global.localStorage.getItem(localStorageKey)) ? {} - return incompatibleNativeModules if incompatibleNativeModules? + if arrayAsString = global.localStorage.getItem(@getIncompatibleNativeModulesStorageKey()) + return JSON.parse(arrayAsString) incompatibleNativeModules = [] for nativeModulePath in @getNativeModuleDependencyPaths() @@ -629,28 +693,9 @@ class Package version: version error: error.message - global.localStorage.setItem(localStorageKey, JSON.stringify({incompatibleNativeModules})) + global.localStorage.setItem(@getIncompatibleNativeModulesStorageKey(), JSON.stringify(incompatibleNativeModules)) incompatibleNativeModules - # Public: Is this package compatible with this version of Atom? - # - # Incompatible packages cannot be activated. This will include packages - # installed to ~/.atom/packages that were built against node 0.11.10 but - # now need to be upgrade to node 0.11.13. - # - # Returns a {Boolean}, true if compatible, false if incompatible. - isCompatible: -> - return @compatible if @compatible? - - if @path.indexOf(path.join(atom.packages.resourcePath, 'node_modules') + path.sep) is 0 - # Bundled packages are always considered compatible - @compatible = true - else if @getMainModulePath() - @incompatibleModules = @getIncompatibleNativeModules() - @compatible = @incompatibleModules.length is 0 - else - @compatible = true - handleError: (message, error) -> if error.filename and error.location and (error instanceof SyntaxError) location = "#{error.filename}:#{error.location.first_line + 1}:#{error.location.first_column + 1}" diff --git a/static/variables/ui-variables.less b/static/variables/ui-variables.less index aa33d0e06..781bac4de 100644 --- a/static/variables/ui-variables.less +++ b/static/variables/ui-variables.less @@ -82,4 +82,4 @@ // Other -@font-family: 'SF UI Text', 'Lucida Grande', 'Segoe UI', Ubuntu, Cantarell, sans-serif; +@font-family: '.SFNSText-Regular', 'SF UI Text', 'Lucida Grande', 'Segoe UI', Ubuntu, Cantarell, sans-serif;