From d95cb45147565f529c057fec25c5173f446dd1a0 Mon Sep 17 00:00:00 2001 From: Evan Morikawa Date: Thu, 3 Dec 2015 11:53:51 -0800 Subject: [PATCH] feat(ci): add Travis and AppVeyor ci support --- .travis.yml | 85 +++++---- appveyor.yml | 49 ++++++ build/Gruntfile.coffee | 29 +-- build/resources/nylas | 2 +- .../ssh/nylas-n1-ci-ssh-secure-file.enc | Bin 0 -> 3264 bytes .../resources/ssh/nylas-n1-ci-ssh.openssl.enc | Bin 0 -> 3264 bytes build/tasks/codesign-task.coffee | 166 +++++++++++++----- build/tasks/publish-nylas-build-task.coffee | 61 ++++--- build/tasks/task-helpers.coffee | 39 +++- script/cibuild.ps1 | 2 + spec_integration/package.json | 1 + 11 files changed, 303 insertions(+), 131 deletions(-) create mode 100644 appveyor.yml create mode 100755 build/resources/ssh/nylas-n1-ci-ssh-secure-file.enc create mode 100644 build/resources/ssh/nylas-n1-ci-ssh.openssl.enc create mode 100644 script/cibuild.ps1 diff --git a/.travis.yml b/.travis.yml index e43b78531..a0c190d0f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,52 +1,38 @@ -env: - global: - - NYLAS_ACCESS_TOKEN=cb200be7c921f73a1c35930f6a4ac8758b271be0 - compiler: - - clang - - gcc - +- clang +- gcc matrix: include: -# Wants a c++11 compiler - - os: linux - env: NODE_VERSION=0.10 CC=gcc-4.8 CXX=g++-4.8 NYLAS_HOME=/home/travis/.nylas - - os: linux - env: NODE_VERSION=0.12 CC=gcc-4.8 CXX=g++-4.8 NYLAS_HOME=/home/travis/.nylas - - os: linux - env: NODE_VERSION=4.2 CC=gcc-4.8 CXX=g++-4.8 NYLAS_HOME=/home/travis/.nylas - - os: linux - env: NODE_VERSION=5 CC=gcc-4.8 CXX=g++-4.8 NYLAS_HOME=/home/travis/.nylas - - os: osx - env: NODE_VERSION=0.10 - - os: osx - env: NODE_VERSION=0.12 CC=clang CXX=clang++ -# osx already supports c++11 in Apple LLVM - - os: osx - env: NODE_VERSION=4.2 CC=clang CXX=clang++ - - os: osx - env: NODE_VERSION=5 CC=clang CXX=clang++ - + - os: linux + env: NODE_VERSION=0.10 CC=gcc-4.8 CXX=g++-4.8 NYLAS_HOME=/home/travis/.nylas + - os: linux + env: NODE_VERSION=0.12 CC=gcc-4.8 CXX=g++-4.8 NYLAS_HOME=/home/travis/.nylas + - os: linux + env: NODE_VERSION=4.2 CC=gcc-4.8 CXX=g++-4.8 NYLAS_HOME=/home/travis/.nylas PUBLISH_BUILD=true + - os: linux + env: NODE_VERSION=5 CC=gcc-4.8 CXX=g++-4.8 NYLAS_HOME=/home/travis/.nylas + - os: osx + env: NODE_VERSION=0.10 + - os: osx + env: NODE_VERSION=0.12 CC=clang CXX=clang++ + - os: osx + env: NODE_VERSION=4.2 CC=clang CXX=clang++ PUBLISH_BUILD=true + - os: osx + env: NODE_VERSION=5 CC=clang CXX=clang++ sudo: false - install: - - echo $CC - - echo $CXX - - clang --version - - if [ "$CC" = "gcc-4.8" ]; then gcc-4.8 -v; else gcc -v; fi - - git clone https://github.com/creationix/nvm.git /tmp/.nvm - - source /tmp/.nvm/nvm.sh - - nvm install $NODE_VERSION - - nvm use --delete-prefix $NODE_VERSION - +- echo $CC +- echo $CXX +- clang --version +- if [ "$CC" = "gcc-4.8" ]; then gcc-4.8 -v; else gcc -v; fi +- git clone https://github.com/creationix/nvm.git /tmp/.nvm +- source /tmp/.nvm/nvm.sh +- nvm install $NODE_VERSION +- nvm use --delete-prefix $NODE_VERSION before_script: - - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then - export DISPLAY=:99.0; - sh -e /etc/init.d/xvfb start; - fi - +- if [ "${TRAVIS_OS_NAME}" == "linux" ]; then export DISPLAY=:99.0; sh -e /etc/init.d/xvfb + start; fi script: script/cibuild - addons: apt: sources: @@ -59,11 +45,22 @@ addons: - git - libgnome-keyring-dev - xvfb - branches: only: - master - ci-test - git: submodules: false +before_install: +- openssl aes-256-cbc -k "$DECRYPTION_PASSWORD" -in build/resources/ssh/nylas-n1-ci-ssh.openssl.enc -out nylas-n1-ci-ssh -d +- mv nylas-n1-ci-ssh ~/.ssh/id_rsa +- ssh-keyscan github.com >> ~/.ssh/known_hosts +- chmod 400 ~/.ssh/id_rsa +- git submodule update --init --recursive +- 2>/dev/null 1>/dev/null openssl aes-256-cbc -K $encrypted_d583b56b822e_key -iv $encrypted_d583b56b822e_iv -in build/resources/nylas/encrypted_certificates/travis/travis-files.tar.enc -out build/resources/nylas/encrypted_certificates/travis/travis-files.tar -d +- mkdir build/resources/certs +- 2>/dev/null 1>/dev/null tar xvf build/resources/nylas/encrypted_certificates/travis/travis-files.tar --directory=build/resources/certs/ +- 2>/dev/null 1>/dev/null source build/resources/certs/set_unix_env.sh +env: + global: + secure: O+XG3C/VnCfCVXTHudOQ6JMmBqrRHXh1j7jRwfQ3PYd27gm9BWNjvwlbWTLNcaar9gM00Pwi3rR8IujzJLlw3usZGWbJMLkz+aPYdOlbiDLhyMdIHwX4oI58d1eHx7m8Eun3qT3Y0VkO3blBNdWFWV4ebkfLZyygzXva4CDlSFtbeQYGy4ft76v7Au9uVlOUoV8f+juPx+0Jv+AtQmsY9Sf+6WbxrNaE9y2u2q1ks+XpjFn8Wt1f/xp/Vae0/MjJFpGIVfaUy+q7W8QQ0TyzSCM0eGtjxilS+BkGHjjvlLlMdCspRnZzpSJC+KkpEKLJrAPjR0DAfYMedWockEaIwGG8Onf90fXKG0nlvAg4WrWjnpr0q+V79zOU/yYD/kysLBYg6fYzv6uTvN7TzNaFkKaiQZvorI9P8w/wShGFHE4Y8JC5QU4CLI8q2qrkI38KKt3valIP4qxA/56aM0+D5roTecfh3Y40OcpqKZicpeNvuB1u2FmD2+oxLEb3MVnyfnVnDBP7Crp8/oLsIl/gGR/SVUEyLIimozugUySnCig1BEicygECvp6eRpEHryNrEvLKfxtppr3eWcRKQwnYwch4CaFwya0Lpc1dSA9NaOFQu0kR374s+fhqYgrkaCJevZd+ouKY+0Zt8gZ4CjJVhJ5YamwZAPG+obRd9G3xRM0= diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..ff0d2d8c4 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,49 @@ +version: '{build}' + +platform: + - x86 + +pull_requests: + do_not_increment_build_number: true + +branches: + only: + - master + - ci-test + +test: off + +# We deploy via grunt publish-nylas-build instead of AppVeyor +deploy: off + +# We need to only clone the main module because our submodule requires the +# encrypted ssh key to access submodules +clone_depth: 1 +build: + verbosity: minimal +install: +- ps: Install-Product node $env:NODE_VERSION $env:PLATFORM +- ps: nuget install secure-file -ExcludeVersion + +# We need to extract the encrypted private ssh key to clone the submodule. +- ps: secure-file\tools\secure-file -decrypt build\resources\ssh\nylas-n1-ci-ssh-secure-file.enc -secret $env:DECRYPTION_PASSWORD +- ps: mv -Force build\resources\ssh\nylas-n1-ci-ssh-secure-file c:\users\appveyor\.ssh\id_rsa +- ps: git submodule init +# http://stackoverflow.com/questions/21002919/running-a-remote-powershell-script-with-a-git-command-in-it-results-in-nativecom +- ps: Start-Process -FilePath git.exe -ArgumentList 'submodule update' -Wait -NoNewWindow + +- ps: secure-file\tools\secure-file -decrypt build\resources\nylas\encrypted_certificates\appveyor\win-nylas-n1.p12.enc -secret $env:DECRYPTION_PASSWORD +- ps: secure-file\tools\secure-file -decrypt build\resources\nylas\encrypted_certificates\appveyor\set_win_env.ps1.enc -secret $env:DECRYPTION_PASSWORD +- ps: . build\resources\nylas\encrypted_certificates\appveyor\set_win_env.ps1 + +build_script: +- ps: .\script\cibuild.ps1 + +environment: + matrix: + - NODE_VERSION: 0.12 + PUBLISH_BUILD: true + global: + CERTIFICATE_FILE: .\build\resources\nylas\encrypted_certificates\appveyor\win-nylas-n1.p12 + DECRYPTION_PASSWORD: + secure: 48VSzDtdBd52Xlo3TZ1NeR1yRRrZ3AU6Px5XjD5RDp44cFU5GYVspecGqX6DGCV7i0D7nldGMyEbXNrjM1t1Kw== diff --git a/build/Gruntfile.coffee b/build/Gruntfile.coffee index 990e04ddb..077bede0e 100644 --- a/build/Gruntfile.coffee +++ b/build/Gruntfile.coffee @@ -9,6 +9,9 @@ babelOptions = require '../static/babelrc' # packages in the root-level node_modules are compiled against Chrome's v8 # headers. # +# See build/resources/nylas/docs/ContinuousIntegration.md for more detailed +# instructions on how we build N1. +# # Some useful grunt options are: # --install-dir # --build-dir @@ -37,7 +40,6 @@ babelOptions = require '../static/babelrc' # installDir = /usr/local OR $INSTALL_PREFIX # binDir = /usr/local/bin # shareDir = /usr/local/share/nylas - _ = require 'underscore' packageJson = require '../package.json' @@ -332,7 +334,7 @@ module.exports = (grunt) -> outputDir: 'electron' downloadDir: electronDownloadDir rebuild: true # rebuild native modules after electron is updated - token: process.env.NYLAS_ACCESS_TOKEN + token: process.env.NYLAS_GITHUB_OAUTH_TOKEN 'create-windows-installer': installer: @@ -343,7 +345,7 @@ module.exports = (grunt) -> iconUrl: 'http://edgehill.s3.amazonaws.com/static/nylas.ico' setupIcon: path.resolve(__dirname, 'resources', 'win', 'nylas.ico') certificateFile: process.env.CERTIFICATE_FILE - certificatePassword: process.env.CERTIFICATE_PASSWORD + certificatePassword: process.env.WINDOWS_CODESIGN_KEY_PASSWORD exe: 'nylas.exe' shell: @@ -375,18 +377,25 @@ module.exports = (grunt) -> buildTasks.push('set-exe-icon') if process.platform is 'win32' grunt.registerTask('build', buildTasks) - ciTasks = ['output-disk-space', 'download-electron', 'build'] + ciTasks = ['output-disk-space', + 'download-electron', + 'build'] ciTasks.push('dump-symbols') if process.platform isnt 'win32' ciTasks.push('set-version', 'lint', 'generate-asar') - ciTasks.push('test') if process.platform is 'darwin' - unless process.env.TRAVIS - ciTasks.push('codesign') - ciTasks.push('mkdmg') if process.platform is 'darwin' - ciTasks.push('mkdeb') if process.platform is 'linux' + if process.platform is "darwin" + ciTasks.push('test', 'codesign', 'mkdmg') + + else if process.platform is "linux" + ciTasks.push('mkdeb') # Only works on Fedora build machines # ciTasks.push('mkrpm') if process.platform is 'linux' - ciTasks.push('create-windows-installer:installer') if process.platform is 'win32' + + else if process.platform is "win32" + ciTasks.push('create-windows-installer:installer') + + {shouldPublishBuild} = require('./tasks/task-helpers')(grunt) + if shouldPublishBuild() ciTasks.push('publish-nylas-build') grunt.registerTask('ci', ciTasks) diff --git a/build/resources/nylas b/build/resources/nylas index 2c6ed6956..48e33fad6 160000 --- a/build/resources/nylas +++ b/build/resources/nylas @@ -1 +1 @@ -Subproject commit 2c6ed69565b82a5f9bdd6f3ae675070a797f5cdd +Subproject commit 48e33fad632565aa1ebfbfaa84e555c36a7d6572 diff --git a/build/resources/ssh/nylas-n1-ci-ssh-secure-file.enc b/build/resources/ssh/nylas-n1-ci-ssh-secure-file.enc new file mode 100755 index 0000000000000000000000000000000000000000..679920ac840050ae13c1558e1c9c5facaaa9de67 GIT binary patch literal 3264 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 literal 0 HcmV?d00001 diff --git a/build/resources/ssh/nylas-n1-ci-ssh.openssl.enc b/build/resources/ssh/nylas-n1-ci-ssh.openssl.enc new file mode 100644 index 0000000000000000000000000000000000000000..935f01e4a61cfa2932c8beff74dfb3a7152b112f GIT binary patch literal 3264 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 literal 0 HcmV?d00001 diff --git a/build/tasks/codesign-task.coffee b/build/tasks/codesign-task.coffee index 63bf636ee..30534d78b 100644 --- a/build/tasks/codesign-task.coffee +++ b/build/tasks/codesign-task.coffee @@ -1,61 +1,133 @@ +_ = require 'underscore' path = require 'path' fs = require 'fs-plus' -# Edgehill introduces the KEYCHAIN_ACCESS environment variable. This is -# injected via Jenkins. It is of the form: +# Codesigning is a Mac-only process that requires a valid Apple +# certificate, the private key, and access to the Mac keychain. +# +# We can only codesign from keys in the keychain. At the end of the day we need +# the certificate and private key to exist in the keychain, and these two +# variables to be set: +# +# XCODE_KEYCHAIN - The path to the keychain we're using +# XCODE_KEYCHAIN_PASSWORD - The keychain password +# +# If these variables are already set we don't have to do anything. +# +# If the keychain and password already exists, we'll know by detecting the +# KEYCHAIN_ACCESS environment variable. It is of the form: # # /full/keychain/path/login.keychain:password # -# The KEYCHAIN_ACCESS variable is saved in a Jenkins Credential and -# injected via the Credentials Binding Plugin. +# In the case of Travis, we need to setup a temp keychain from encrypted files +# in the repository. # We'll decrypt and import our certificates, put them in +# a temporary keychain, and use that. # +# If you want to verify the app was signed you can run the commands: +# +# spctl -a -t exec -vv /path/to/N1.app +# +# Which should return "satisfies its Designated Requirement" +# +# And: +# +# codesign --verify --deep --verbose=2 /path/to/N1.app +# +# Which should return "accepted" module.exports = (grunt) -> - {spawn} = require('./task-helpers')(grunt) + {spawnP, shouldPublishBuild} = require('./task-helpers')(grunt) + tmpKeychain = "n1-build.keychain" grunt.registerTask 'codesign', 'Codesign the app', -> done = @async() + return unless process.platform is 'darwin' - if process.platform is 'darwin' and (process.env.XCODE_KEYCHAIN or process.env.KEYCHAIN_ACCESS) - unlockKeychain (error) -> - if error? - done(error) - else - signApp(done) - else - signApp(done) - - unlockKeychain = (callback) -> - cmd = 'security' - {XCODE_KEYCHAIN_PASSWORD, XCODE_KEYCHAIN, KEYCHAIN_ACCESS} = process.env - - if KEYCHAIN_ACCESS? + {XCODE_KEYCHAIN_PASSWORD, XCODE_KEYCHAIN} = process.env + if XCODE_KEYCHAIN? and XCODE_KEYCHAIN_PASSWORD? + unlockKeychain(XCODE_KEYCHAIN, XCODE_KEYCHAIN_PASSWORD) + .then(signApp) + .then(verifyApp).then(done).catch(grunt.fail.fatal) + else if process.env.KEYCHAIN_ACCESS? [XCODE_KEYCHAIN, XCODE_KEYCHAIN_PASSWORD] = KEYCHAIN_ACCESS.split(":") - - args = ['unlock-keychain', '-p', XCODE_KEYCHAIN_PASSWORD, XCODE_KEYCHAIN] - spawn {cmd, args}, (error) -> callback(error) - - signApp = (callback) -> - switch process.platform - when 'darwin' - cmd = 'codesign' - args = ['--deep', '--force', '--verbose', '--sign', 'Developer ID Application: InboxApp, Inc.', grunt.config.get('nylasGruntConfig.shellAppDir')] - spawn {cmd, args}, (error) -> callback(error) - when 'win32' - # TODO: Don't do anything now, because we need a certificate pfx file - # issued from a certificate authority, and we don't have one. - return callback() - spawn {cmd: 'taskkill', args: ['/F', '/IM', 'nylas.exe']}, -> - cmd = process.env.JANKY_SIGNTOOL ? 'signtool' - args = ['sign', path.join(grunt.config.get('nylasGruntConfig.shellAppDir'), 'nylas.exe')] - - spawn {cmd, args}, (error) -> - return callback(error) if error? - - setupExePath = path.resolve(grunt.config.get('nylasGruntConfig.buildDir'), 'installer', 'NylasSetup.exe') - if fs.isFileSync(setupExePath) - args = ['sign', setupExePath] - spawn {cmd, args}, (error) -> callback(error) - else - callback() + unlockKeychain(XCODE_KEYCHAIN, XCODE_KEYCHAIN_PASSWORD) + .then(signApp) + .then(verifyApp).then(done).catch(grunt.fail.fatal) + else if process.env.TRAVIS + if shouldPublishBuild() + buildTravisKeychain() + .then(_.partial(signApp, tmpKeychain)) + .then(verifyApp) + .then(cleanupKeychain) + .then(done).catch(grunt.fail.fatal) else - callback() + return done() + else + grunt.fail.fatal("Can't codesign without keychain or certs") + + unlockKeychain = (keychain, keychainPass) -> + args = ['unlock-keychain', '-p', keychainPass, keychain] + spawnP("security", args) + + signApp = (keychain) -> + devId = 'Developer ID Application: InboxApp, Inc.' + appPath = grunt.config.get('nylasGruntConfig.shellAppDir') + args = ['--deep', '--force', '--verbose', '--sign', devId] + args.push("--keychain", keychain) if keychain + args.push(appPath) + spawnP("codesign", args) + + buildTravisKeychain = -> + crypto = require('crypto') + tmpPass = crypto.randomBytes(32).toString('hex') + {appleCert, nylasCert, nylasPrivateKey, keyPass} = getCertData() + codesignBin = path.join("/", "usr","bin", "codesign") + + # Create a custom, temporary keychain + cleanupKeychain() + .then -> spawnP("security", ["create-keychain", '-p', tmpPass, tmpKeychain]) + + # Make the custom keychain default, so xcodebuild will use it for signing + .then -> spawnP("security", ["default-keychain", "-s", tmpKeychain]) + + # Unlock the keychain + .then -> unlockKeychain(tmpKeychain, tmpPass) + + # Set keychain timeout to 1 hour for long builds + .then -> spawnP("security", ["set-keychain-settings", "-t", "3600", "-l", tmpKeychain]) + + # Add certificates to keychain and allow codesign to access them + .then -> spawnP("security", ["import", appleCert, "-k", tmpKeychain, "-T", codesignBin]) + + .then -> spawnP("security", ["import", nylasCert, "-k", tmpKeychain, "-T", codesignBin]) + + # Load the password for the private key from environment variables + .then -> spawnP("security", ["import", nylasPrivateKey, "-k", tmpKeychain, "-P", keyPass, "-T", codesignBin]) + + verifyApp = -> + appPath = grunt.config.get('nylasGruntConfig.shellAppDir') + spawnP("codesign", ["--verify", "--deep", "--verbose=2", appPath]) + .then -> spawnP("spctl", ["-a", "-t", "exec", "-vv", appPath]) + + cleanupKeychain = -> + if fs.existsSync(path.join(process.env.HOME, "Library", "Keychains", tmpKeychain)) + return spawnP("security", ["delete-keychain", tmpKeychain]) + else return Promise.resolve() + + getCertData = -> + certs = path.resolve(path.join('build', 'resources', 'certs')) + appleCert = path.join(certs, 'AppleWWDRCA.cer') + nylasCert = path.join(certs, 'mac-nylas-n1.cer') + nylasPrivateKey = path.join(certs, 'mac-nylas-n1.p12') + + keyPass = process.env.APPLE_CODESIGN_KEY_PASSWORD + + if not keyPass + throw new Error("APPLE_CODESIGN_KEY_PASSWORD must be set") + if not fs.existsSync(appleCert) + throw new Error("#{appleCert} doesn't exist") + if not fs.existsSync(nylasCert) + throw new Error("#{nylasCert} doesn't exist") + if not fs.existsSync(nylasPrivateKey) + throw new Error("#{nylasPrivateKey} doesn't exist") + + return {appleCert, nylasCert, nylasPrivateKey, keyPass} diff --git a/build/tasks/publish-nylas-build-task.coffee b/build/tasks/publish-nylas-build-task.coffee index 6365f0981..d87c165a5 100644 --- a/build/tasks/publish-nylas-build-task.coffee +++ b/build/tasks/publish-nylas-build-task.coffee @@ -51,10 +51,10 @@ module.exports = (grunt) -> resolve() postToSlack = (msg) -> - return Promise.resolve() unless process.env.PUBLISH_SLACK_HOOK + return Promise.resolve() unless process.env.NYLAS_INTERNAL_HOOK_URL new Promise (resolve, reject) -> request.post - url: process.env.PUBLISH_SLACK_HOOK + url: process.env.NYLAS_INTERNAL_HOOK_URL json: username: "Edgehill Builds" text: msg @@ -126,11 +126,9 @@ module.exports = (grunt) -> awsSecret = process.env.AWS_SECRET_ACCESS_KEY ? "" if awsKey.length is 0 - grunt.log.error "Please set the AWS_ACCESS_KEY_ID environment variable" - return false + grunt.fail.fatal "Please set the AWS_ACCESS_KEY_ID environment variable" if awsSecret.length is 0 - grunt.log.error "Please set the AWS_SECRET_ACCESS_KEY environment variable" - return false + grunt.fail.fatal "Please set the AWS_SECRET_ACCESS_KEY environment variable" s3Client = s3.createClient s3Options: @@ -139,25 +137,34 @@ module.exports = (grunt) -> done = @async() - populateVersion().then -> - runEmailIntegrationTest().then -> - uploadPromises = [] - if process.platform is 'darwin' - uploadPromises.push uploadToS3(dmgName(), "#{fullVersion}/#{process.platform}/#{process.arch}/N1.dmg") - uploadPromises.push uploadZipToS3(appName(), "#{fullVersion}/#{process.platform}/#{process.arch}/N1.zip") - if process.platform is 'win32' - uploadPromises.push uploadToS3("installer/"+winReleasesName(), "#{fullVersion}/#{process.platform}/#{process.arch}/RELEASES") - uploadPromises.push uploadToS3("installer/"+winSetupName(), "#{fullVersion}/#{process.platform}/#{process.arch}/N1Setup.exe") - uploadPromises.push uploadToS3("installer/"+winNupkgName(), "#{fullVersion}/#{process.platform}/#{process.arch}/#{winNupkgName()}") - if process.platform is 'linux' - buildDir = grunt.config.get('nylasGruntConfig.buildDir') - files = fs.readdirSync(buildDir) - for file in files - if path.extname(file) is '.deb' - uploadPromises.push uploadToS3(file, "#{fullVersion}/#{process.platform}/#{process.arch}/N1.deb") - if path.extname(file) is '.rpm' - uploadPromises.push uploadToS3(file, "#{fullVersion}/#{process.platform}/#{process.arch}/N1.rpm") + populateVersion() + .then -> + if process.env.RUN_APPLE_SCRIPT_INTEGRATION + runEmailIntegrationTest() + else Promise.resolve() + .then -> + uploadPromises = [] + if process.platform is 'darwin' + uploadPromises.push uploadToS3(dmgName(), "#{fullVersion}/#{process.platform}/#{process.arch}/N1.dmg") + uploadPromises.push uploadZipToS3(appName(), "#{fullVersion}/#{process.platform}/#{process.arch}/N1.zip") - Promise.all(uploadPromises).then(done).catch (err) -> - grunt.log.error(err) - return false + else if process.platform is 'win32' + uploadPromises.push uploadToS3("installer/"+winReleasesName(), "#{fullVersion}/#{process.platform}/#{process.arch}/RELEASES") + uploadPromises.push uploadToS3("installer/"+winSetupName(), "#{fullVersion}/#{process.platform}/#{process.arch}/N1Setup.exe") + uploadPromises.push uploadToS3("installer/"+winNupkgName(), "#{fullVersion}/#{process.platform}/#{process.arch}/#{winNupkgName()}") + + else if process.platform is 'linux' + buildDir = grunt.config.get('nylasGruntConfig.buildDir') + files = fs.readdirSync(buildDir) + for file in files + if path.extname(file) is '.deb' + uploadPromises.push uploadToS3(file, "#{fullVersion}/#{process.platform}/#{process.arch}/N1.deb") + if path.extname(file) is '.rpm' + uploadPromises.push uploadToS3(file, "#{fullVersion}/#{process.platform}/#{process.arch}/N1.rpm") + + else + grunt.fail.fatal "Unsupported platform: '#{process.platform}'" + + Promise.all(uploadPromises).then(done).catch (err) -> + grunt.log.error(err) + return false diff --git a/build/tasks/task-helpers.coffee b/build/tasks/task-helpers.coffee index fa0fb6f92..23c5a916e 100644 --- a/build/tasks/task-helpers.coffee +++ b/build/tasks/task-helpers.coffee @@ -61,6 +61,14 @@ module.exports = (grunt) -> grunt.log.error results.stderr if exitCode != 0 callback(error, results, exitCode) + spawnP: (cmd, args=[], opts={}) -> new Promise (resolve, reject) -> + childProcess = require 'child_process' + opts.stdio ?= 'inherit' + proc = childProcess.spawn(cmd, args, opts) + proc.on 'error', grunt.fail.fatal + proc.on 'close', (exitCode, signal) -> + if exitCode is 0 then resolve() else reject(exitCode) + isNylasPackage: (packagePath) -> try {engines} = grunt.file.readJSON(path.join(packagePath, 'package.json')) @@ -69,12 +77,39 @@ module.exports = (grunt) -> false notifyAPI: (msg, callback) -> - if (process.env("TEST_ERROR_HOOK_URL") ? "").length > 0 + if (process.env("NYLAS_INTERNAL_HOOK_URL") ? "").length > 0 request.post - url: process.env("TEST_ERROR_HOOK_URL") + url: process.env("NYLAS_INTERNAL_HOOK_URL") json: username: "Edgehill Builds" text: msg , callback else callback() + shouldPublishBuild: -> + return false unless process.env.PUBLISH_BUILD + if process.env.APPVEYOR + if process.env.APPVEYOR_PULL_REQUEST_NUMBER + grunt.log.writeln("Skipping because this is a pull request") + return false + + branch = process.env.APPVEYOR_REPO_BRANCH + if branch is "master" or branch is "ci-test" + return true + else + grunt.log.writeln("Skipping. We don't operate on branch '#{branch}''") + return false + + else if process.env.TRAVIS + if process.env.TRAVIS_PULL_REQUEST isnt "false" + grunt.log.writeln("Skipping because this is a pull request") + return false + + branch = process.env.TRAVIS_BRANCH + if branch is "master" or branch is "ci-test" + return true + else + grunt.log.writeln("Skipping. We don't operate on branch '#{branch}''") + return false + return true + diff --git a/script/cibuild.ps1 b/script/cibuild.ps1 new file mode 100644 index 000000000..5b793db9b --- /dev/null +++ b/script/cibuild.ps1 @@ -0,0 +1,2 @@ +.\script\bootstrap.cmd +.\build\node_modules\.bin\grunt ci --gruntfile .\build\Gruntfile.coffee --statck --no-color diff --git a/spec_integration/package.json b/spec_integration/package.json index ecde74ed7..93a7df364 100644 --- a/spec_integration/package.json +++ b/spec_integration/package.json @@ -8,6 +8,7 @@ "scripts": { "test": "jasmine JASMINE_CONFIG_PATH=./config.json" }, + "license": "GPL-3.0", "dependencies": { "bluebird": "^3.0.5", "babel-core": "^5.8.21",