Skip to content
This repository has been archived by the owner on Dec 15, 2022. It is now read-only.

Commit

Permalink
Merge pull request #200 from atom/ks-install-cache
Browse files Browse the repository at this point in the history
Generate module cache during install
  • Loading branch information
kevinsawicki committed Oct 15, 2014
2 parents 59a9369 + d380f82 commit ea11c0a
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 10 deletions.
4 changes: 2 additions & 2 deletions Gruntfile.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,6 @@ module.exports = (grunt) ->
grunt.file.delete('bin/node_darwin_x64') if grunt.file.exists('bin/node_darwin_x64')

grunt.registerTask('lint', ['coffeelint'])
grunt.registerTask('default', ['coffee', 'coffeelint:src'])
grunt.registerTask('test', ['clean', 'default', 'coffeelint:test', 'shell:test'])
grunt.registerTask('default', ['coffee', 'lint'])
grunt.registerTask('test', ['clean', 'default', 'shell:test'])
grunt.registerTask('prepublish', ['clean', 'coffee', 'lint'])
26 changes: 26 additions & 0 deletions spec/fixtures/install-multi-version.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"releases": {
"latest": "0.4.0"
},
"name": "multi-module",
"versions": {
"0.4.0": {
"engines": {
"atom": ">=2.0"
}
},
"0.3.0": {
"engines": {
"atom": ">=1.0"
},
"dist": {
"tarball": "http://localhost:3000/tarball/test-module-1.0.0.tgz"
}
},
"0.2.0": {
"engines": {
"atom": ">=1.0"
}
}
}
}
50 changes: 49 additions & 1 deletion spec/install-spec.coffee
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
path = require 'path'
CSON = require 'season'
fs = require 'fs-plus'
temp = require 'temp'
express = require 'express'
Expand All @@ -7,7 +8,7 @@ wrench = require 'wrench'
apm = require '../lib/apm-cli'

describe 'apm install', ->
atomHome = null
[atomHome, resourcePath] = []

beforeEach ->
spyOnToken()
Expand All @@ -16,6 +17,9 @@ describe 'apm install', ->
atomHome = temp.mkdirSync('apm-home-dir-')
process.env.ATOM_HOME = atomHome

resourcePath = temp.mkdirSync('atom-resource-path-')
process.env.ATOM_RESOURCE_PATH = resourcePath

describe "when installing an atom package", ->
server = null

Expand All @@ -41,6 +45,8 @@ describe 'apm install', ->
response.sendfile path.join(__dirname, 'fixtures', 'test-module-with-symlink-5.0.0.tgz')
app.get '/tarball/test-module-with-bin-2.0.0.tgz', (request, response) ->
response.sendfile path.join(__dirname, 'fixtures', 'test-module-with-bin-2.0.0.tgz')
app.get '/packages/multi-module', (request, response) ->
response.sendfile path.join(__dirname, 'fixtures', 'install-multi-version.json')

server = http.createServer(app)
server.listen(3000)
Expand Down Expand Up @@ -86,6 +92,35 @@ describe 'apm install', ->
expect(fs.existsSync(path.join(testModuleDirectory, 'package.json'))).toBeTruthy()
expect(callback.mostRecentCall.args[0]).toBeNull()

describe 'when multiple releases are available', ->
it 'installs the latest compatible version', ->
CSON.writeFileSync(path.join(resourcePath, 'package.json'), version: '1.5.0')
packageDirectory = path.join(atomHome, 'packages', 'test-module')

callback = jasmine.createSpy('callback')
apm.run(['install', 'multi-module'], callback)

waitsFor 'waiting for install to complete', 600000, ->
callback.callCount is 1

runs ->
expect(JSON.parse(fs.readFileSync(path.join(packageDirectory, 'package.json'))).version).toBe "1.0.0"
expect(callback.mostRecentCall.args[0]).toBeNull()

it 'logs an error when no compatible versions are available', ->
CSON.writeFileSync(path.join(resourcePath, 'package.json'), version: '0.9.0')
packageDirectory = path.join(atomHome, 'packages', 'test-module')

callback = jasmine.createSpy('callback')
apm.run(['install', 'multi-module'], callback)

waitsFor 'waiting for install to complete', 600000, ->
callback.callCount is 1

runs ->
expect(fs.existsSync(packageDirectory)).toBeFalsy()
expect(callback.mostRecentCall.args[0]).not.toBeNull()

describe 'when multiple package names are specified', ->
it 'installs all packages', ->
testModuleDirectory = path.join(atomHome, 'packages', 'test-module')
Expand Down Expand Up @@ -200,3 +235,16 @@ describe 'apm install', ->

runs ->
expect(callback.mostRecentCall.args[0]).not.toBeNull()

describe 'when the package is bundled with Atom', ->
it 'logs a message to standard error', ->
CSON.writeFileSync(path.join(resourcePath, 'package.json'), packageDependencies: 'test-module': '1.0')

callback = jasmine.createSpy('callback')
apm.run(['install', 'test-module'], callback)

waitsFor 'waiting for install to complete', 600000, ->
callback.callCount is 1

runs ->
expect(console.error.mostRecentCall.args[0].length).toBeGreaterThan 0
1 change: 1 addition & 0 deletions src/apm-cli.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ commandClasses = [
require './login'
require './publish'
require './rebuild'
require './rebuild-module-cache'
require './search'
require './star'
require './stars'
Expand Down
3 changes: 2 additions & 1 deletion src/dedupe.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,9 @@ class Dedupe extends Command
fs.makeTreeSync(@atomNodeDirectory)

run: (options) ->
{callback} = options
{callback, cwd} = options
options = @parseOptions(options.commandArgs)
options.cwd = cwd

@createAtomDirectories()

Expand Down
92 changes: 87 additions & 5 deletions src/install.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ async = require 'async'
_ = require 'underscore-plus'
optimist = require 'optimist'
CSON = require 'season'
semver = require 'semver'
temp = require 'temp'

config = require './config'
Command = require './command'
fs = require './fs'
RebuildModuleCache = require './rebuild-module-cache'
request = require './request'

module.exports =
Expand Down Expand Up @@ -125,6 +127,10 @@ class Install extends Command
destination = path.join(@atomPackagesDirectory, child)
do (source, destination) ->
commands.push (callback) -> fs.cp(source, destination, callback)

commands.push (callback) => @buildModuleCache(pack.name, callback)
commands.push (callback) => @warmCompileCache(pack.name, callback)

async.waterfall commands, (error) =>
if error?
@logFailure()
Expand Down Expand Up @@ -184,7 +190,7 @@ class Install extends Command
message = body.message ? body.error ? body
callback("Request for package information failed: #{message}")
else
if latestVersion = body.releases.latest
if body.releases.latest
callback(null, body)
else
callback("No releases available for #{packageName}")
Expand Down Expand Up @@ -273,14 +279,18 @@ class Install extends Command
@logFailure()
callback(error)
else
commands = []
packageVersion ?= pack.releases.latest
packageVersion ?= @getLatestCompatibleVersion(pack)
unless packageVersion
@logFailure()
callback("No available version compatible with the installed Atom version: #{@installedAtomVersion}")

{tarball} = pack.versions[packageVersion]?.dist ? {}
unless tarball
@logFailure()
callback("Package version: #{packageVersion} not found")
return

commands = []
commands.push (callback) =>
if packagePath = @getPackageCachePath(packageName, packageVersion)
callback(null, packagePath)
Expand Down Expand Up @@ -376,6 +386,68 @@ class Install extends Command
packages = fs.readFileSync(filePath, 'utf8')
@sanitizePackageNames(packages.split(/\s/))

getResourcePath: (callback) ->
if @resourcePath
process.nextTick => callback(@resourcePath)
else
config.getResourcePath (@resourcePath) => callback(@resourcePath)

buildModuleCache: (packageName, callback) ->
packageDirectory = path.join(@atomPackagesDirectory, packageName)
rebuildCacheCommand = new RebuildModuleCache()
rebuildCacheCommand.rebuild packageDirectory, ->
# Ignore cache errors and just finish the install
callback()

warmCompileCache: (packageName, callback) ->
packageDirectory = path.join(@atomPackagesDirectory, packageName)

@getResourcePath (resourcePath) ->
try
CoffeeCache = require(path.join(resourcePath, 'src', 'coffee-cache'))

onDirectory = (directoryPath) ->
path.basename(directoryPath) isnt 'node_modules'

onFile = (filePath) ->
CoffeeCache.addPathToCache(filePath)

fs.traverseTreeSync(packageDirectory, onFile, onDirectory)
callback(null)

isBundledPackage: (packageName, callback) ->
@getResourcePath (resourcePath) ->
try
atomMetadata = JSON.parse(fs.readFileSync(path.join(resourcePath, 'package.json')))
catch error
return callback(false)

callback(atomMetadata?.packageDependencies?.hasOwnProperty(packageName))

getLatestCompatibleVersion: (pack) ->
return pack.releases.latest unless @installedAtomVersion

latestVersion = null
for version, metadata of pack.versions ? {}
continue unless semver.valid(version)
continue unless metadata

engine = metadata.engines?.atom ? '*'
continue unless semver.validRange(engine)
continue unless semver.satisfies(@installedAtomVersion, engine)

latestVersion ?= version
latestVersion = version if semver.gt(version, latestVersion)

latestVersion

loadInstalledAtomVersion: (callback) ->
@getResourcePath (resourcePath) =>
try
{version} = require(path.join(resourcePath, 'package.json')) ? {}
@installedAtomVersion = version if semver.valid(version)
callback()

run: (options) ->
{callback} = options
options = @parseOptions(options.commandArgs)
Expand All @@ -393,9 +465,16 @@ class Install extends Command
if atIndex > 0
version = name.substring(atIndex + 1)
name = name.substring(0, atIndex)
@installPackage({name, version}, options, callback)

commands = []
@isBundledPackage name, (isBundledPackage) =>
if isBundledPackage
console.error """
The #{name} package is bundled with Atom and should not be explicitly installed.
You can run `apm uninstall #{name}` to uninstall it and then the version bundled
with Atom will be used.
""".yellow
@installPackage({name, version}, options, callback)

if packagesFilePath
try
packageNames = @packageNamesFromPath(packagesFilePath)
Expand All @@ -404,6 +483,9 @@ class Install extends Command
else
packageNames = @packageNamesFromArgv(options.argv)
packageNames.push('.') if packageNames.length is 0

commands = []
commands.push (callback) => @loadInstalledAtomVersion(callback)
packageNames.forEach (packageName) ->
commands.push (callback) -> installPackage(packageName, callback)
async.waterfall(commands, callback)
5 changes: 4 additions & 1 deletion src/links.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ class Links extends Command
realpath = '???'.red
"#{path.basename(link).yellow} -> #{realpath}"

run: ->
run: (options) ->
{callback} = options

@logLinks(@devPackagesPath)
@logLinks(@packagesPath)
callback()
48 changes: 48 additions & 0 deletions src/rebuild-module-cache.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
path = require 'path'
async = require 'async'
Command = require './command'
config = require './config'
fs = require './fs'

module.exports =
class RebuildModuleCache extends Command
@commandNames: ['rebuild-module-cache']

constructor: ->
@atomPackagesDirectory = path.join(config.getAtomDirectory(), 'packages')

getResourcePath: (callback) ->
if @resourcePath
process.nextTick => callback(@resourcePath)
else
config.getResourcePath (@resourcePath) => callback(@resourcePath)

rebuild: (packageDirectory, callback) ->
@getResourcePath (resourcePath) =>
try
@moduleCache ?= require(path.join(resourcePath, 'src', 'module-cache'))
@moduleCache.create(packageDirectory)
catch error
return callback(error)

callback()

run: (options) ->
{callback} = options

commands = []
fs.list(@atomPackagesDirectory).forEach (packageName) =>
packageDirectory = path.join(@atomPackagesDirectory, packageName)
return if fs.isSymbolicLinkSync(packageDirectory)
return unless fs.isDirectorySync(packageDirectory)

commands.push (callback) =>
process.stdout.write "Rebuilding #{packageName} module cache "
@rebuild packageDirectory, (error) =>
if error?
@logFailure()
else
@logSuccess()
callback(error)

async.waterfall(commands, callback)

0 comments on commit ea11c0a

Please sign in to comment.