Browse Source

chore: update release scripts to support sudowoodo (#14170)

Backports the totality of our release script changes to support sudowoodo. Also backports changes that have been made to a few other release script files in master after 3-0-x was cut with the purpose of keeping them in sync.
Shelley Vohr 6 years ago
parent
commit
2ecdf4a0eb

+ 2 - 0
script/bootstrap.py

@@ -169,6 +169,8 @@ def setup_libchromiumcontent(is_dev, target_arch, url,
     mkdir_p(target_dir)
   else:
     mkdir_p(DOWNLOAD_DIR)
+  if is_verbose_mode():
+    args += ['-v']
   if is_dev:
     subprocess.check_call([sys.executable, script] + args)
   else:

+ 56 - 34
script/bump-version.py

@@ -5,12 +5,12 @@ import re
 import sys
 import argparse
 
-from lib.util import execute, get_electron_version, parse_version, scoped_cwd
-
+from lib.util import execute, get_electron_version, parse_version, scoped_cwd, \
+is_nightly, is_beta, is_stable, get_next_nightly, get_next_beta, \
+get_next_stable_from_pre, get_next_stable_from_stable, clean_parse_version
 
 SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
 
-
 def main():
 
   parser = argparse.ArgumentParser(
@@ -34,14 +34,7 @@ def main():
     action='store',
     default=None,
     dest='bump',
-    help='increment [major | minor | patch | beta]'
-  )
-  parser.add_argument(
-    '--stable',
-    action='store_true',
-    default= False,
-    dest='stable',
-    help='promote to stable (i.e. remove `-beta.x` suffix)'
+    help='increment [stable | beta | nightly]'
   )
   parser.add_argument(
     '--dry-run',
@@ -52,36 +45,55 @@ def main():
   )
 
   args = parser.parse_args()
+  curr_version = get_electron_version()
+
+  if args.bump not in ['stable', 'beta', 'nightly']:
+    raise Exception('bump must be set to either stable, beta or nightly')
+
+  if is_nightly(curr_version):
+    if args.bump == 'nightly':
+      version = get_next_nightly(curr_version)
+    elif args.bump == 'beta':
+      version = get_next_beta(curr_version)
+    elif args.bump == 'stable':
+      version = get_next_stable_from_pre(curr_version)
+    else:
+      not_reached()
+  elif is_beta(curr_version):
+    if args.bump == 'nightly':
+      version = get_next_nightly(curr_version)
+    elif args.bump == 'beta':
+      version = get_next_beta(curr_version)
+    elif args.bump == 'stable':
+      version = get_next_stable_from_pre(curr_version)
+    else:
+      not_reached()
+  elif is_stable(curr_version):
+    if args.bump == 'nightly':
+      version = get_next_nightly(curr_version)
+    elif args.bump == 'beta':
+      raise Exception("You can\'t bump to a beta from stable")
+    elif args.bump == 'stable':
+      version = get_next_stable_from_stable(curr_version)
+    else:
+      not_reached()
+  else:
+    raise Exception("Invalid current version: " + curr_version)
 
   if args.new_version == None and args.bump == None and args.stable == False:
     parser.print_help()
     return 1
 
-  increments = ['major', 'minor', 'patch', 'beta']
-
-  curr_version = get_electron_version()
-  versions = parse_version(re.sub('-beta', '', curr_version))
-
-  if args.bump in increments:
-    versions = increase_version(versions, increments.index(args.bump))
-    if versions[3] == '0':
-      # beta starts at 1
-      versions = increase_version(versions, increments.index('beta'))
-
-  if args.stable == True:
-    versions[3] = '0'
-
-  if args.new_version != None:
-    versions = parse_version(re.sub('-beta', '', args.new_version))
-
-  version = '.'.join(versions[:3])
-  suffix = '' if versions[3] == '0' else '-beta.' + versions[3]
+  versions = clean_parse_version(version)
+  suffix = ''
+  if '-' in version:
+    suffix = '-' + version.split('-')[1]
+  version = version.split('-')[0]
 
   if args.dry_run:
     print 'new version number would be: {0}\n'.format(version + suffix)
     return 0
 
-
   with scoped_cwd(SOURCE_ROOT):
     update_electron_gyp(version, suffix)
     update_win_rc(version, versions)
@@ -92,6 +104,9 @@ def main():
 
   print 'Bumped to version: {0}'.format(version + suffix)
 
+def not_reached():
+  raise Exception('Unreachable code was reached')
+
 def increase_version(versions, index):
   for i in range(index + 1, 4):
     versions[i] = '0'
@@ -100,7 +115,8 @@ def increase_version(versions, index):
 
 
 def update_electron_gyp(version, suffix):
-  pattern = re.compile(" *'version%' *: *'[0-9.]+(-beta[0-9.]*)?'")
+  pattern = re.compile(" *'version%' *: *'[0-9.]+(-beta[0-9.]*)?(-dev)?"
+    + "(-nightly[0-9]*)?'")
   with open('electron.gyp', 'r') as f:
     lines = f.readlines()
 
@@ -192,8 +208,14 @@ def update_package_json(version, suffix):
 
 
 def tag_version(version, suffix):
-  commit_message = 'Bump v{0}'.format(version + suffix)
-  execute(['git', 'commit', '-a', '--no-verify', '-m', commit_message])
+  execute([
+    'git',
+    'commit',
+    '-a',
+    '-m',
+    'Bump v{0}'.format(version + suffix),
+    '-n'
+  ])
 
 
 if __name__ == '__main__':

+ 2 - 3
script/ci-release-build.js

@@ -1,3 +1,5 @@
+require('dotenv-safe').load()
+
 const assert = require('assert')
 const request = require('request')
 const buildAppVeyorURL = 'https://windows-ci.electronjs.org/api/builds'
@@ -44,7 +46,6 @@ async function makeRequest (requestOptions, parseResponse) {
 }
 
 async function circleCIcall (buildUrl, targetBranch, job, options) {
-  assert(process.env.CIRCLE_TOKEN, 'CIRCLE_TOKEN not found in environment')
   console.log(`Triggering CircleCI to run build job: ${job} on branch: ${targetBranch} with release flag.`)
   let buildRequest = {
     'build_parameters': {
@@ -77,7 +78,6 @@ async function circleCIcall (buildUrl, targetBranch, job, options) {
 }
 
 function buildAppVeyor (targetBranch, options) {
-  assert(process.env.APPVEYOR_TOKEN, 'APPVEYOR_TOKEN not found in environment')
   const validJobs = Object.keys(appVeyorJobs)
   if (options.job) {
     assert(validJobs.includes(options.job), `Unknown AppVeyor CI job name: ${options.job}.  Valid values are: ${validJobs}.`)
@@ -139,7 +139,6 @@ async function buildVSTS (targetBranch, options) {
     assert(vstsJobs.includes(options.job), `Unknown VSTS CI job name: ${options.job}. Valid values are: ${vstsJobs}.`)
   }
   console.log(`Triggering VSTS to run build on branch: ${targetBranch} with release flag.`)
-  assert(process.env.VSTS_TOKEN, 'VSTS_TOKEN not found in environment')
   let environmentVariables = {}
 
   if (!options.ghRelease) {

+ 3 - 1
script/find-release.js

@@ -1,3 +1,5 @@
+if (!process.env.CI) require('dotenv-safe').load()
+
 const GitHub = require('github')
 const github = new GitHub()
 
@@ -12,7 +14,7 @@ async function findRelease () {
   github.authenticate({type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN})
   let releases = await github.repos.getReleases({
     owner: 'electron',
-    repo: 'electron'
+    repo: version.indexOf('nightly') > 0 ? 'nightlies' : 'electron'
   })
   let targetRelease = releases.data.find(release => {
     return release.tag_name === version

+ 29 - 0
script/get-last-major-for-master.js

@@ -0,0 +1,29 @@
+const { GitProcess } = require('dugite')
+const path = require('path')
+const semver = require('semver')
+const gitDir = path.resolve(__dirname, '..')
+
+async function determineNextMajorForMaster () {
+  let branchNames
+  let result = await GitProcess.exec(['branch', '-a', '--remote', '--list', 'origin/[0-9]-[0-9]-x'], gitDir)
+  if (result.exitCode === 0) {
+    branchNames = result.stdout.trim().split('\n')
+    const filtered = branchNames.map(b => b.replace('origin/', ''))
+    return getNextReleaseBranch(filtered)
+  } else {
+    throw new Error('Release branches could not be fetched.')
+  }
+}
+
+function getNextReleaseBranch (branches) {
+  const converted = branches.map(b => b.replace(/-/g, '.').replace('x', '0'))
+  const next = converted.reduce((v1, v2) => {
+    return semver.gt(v1, v2) ? v1 : v2
+  })
+  return parseInt(next.split('.')[0], 10)
+}
+
+determineNextMajorForMaster().then(console.info).catch((err) => {
+  console.error(err)
+  process.exit(1)
+})

+ 14 - 8
script/lib/dbus_mock.py

@@ -6,17 +6,23 @@ import os
 import sys
 
 
-def cleanup():
+def stop():
     DBusTestCase.stop_dbus(DBusTestCase.system_bus_pid)
     DBusTestCase.stop_dbus(DBusTestCase.session_bus_pid)
 
+def start():
+    dbusmock_log = sys.stdout if is_verbose_mode() else open(os.devnull, 'w')
 
-atexit.register(cleanup)
+    DBusTestCase.start_system_bus()
+    DBusTestCase.spawn_server_template('logind', None, dbusmock_log)
 
-dbusmock_log = sys.stdout if is_verbose_mode() else open(os.devnull, 'w')
+    DBusTestCase.start_session_bus()
+    DBusTestCase.spawn_server_template('notification_daemon', None, dbusmock_log)
 
-DBusTestCase.start_system_bus()
-DBusTestCase.spawn_server_template('logind', None, dbusmock_log)
-
-DBusTestCase.start_session_bus()
-DBusTestCase.spawn_server_template('notification_daemon', None, dbusmock_log)
+if __name__ == '__main__':
+    import subprocess
+    start()
+    try:
+        subprocess.check_call(sys.argv[1:])
+    finally:
+        stop()

+ 66 - 1
script/lib/util.py

@@ -2,6 +2,7 @@
 
 import atexit
 import contextlib
+import datetime
 import errno
 import platform
 import re
@@ -87,7 +88,7 @@ def download(text, url, path):
     downloaded_size = 0
     block_size = 128
 
-    ci = os.environ.get('CI') == '1'
+    ci = os.environ.get('CI') is not None
 
     while True:
       buf = web_file.read(block_size)
@@ -293,3 +294,67 @@ def update_node_modules(dirname, env=None):
         pass
     else:
       execute_stdout(args, env)
+
+def clean_parse_version(v):
+  return parse_version(v.split("-")[0])        
+
+def is_stable(v):
+  return len(v.split(".")) == 3    
+
+def is_beta(v):
+  return 'beta' in v
+
+def is_nightly(v):
+  return 'nightly' in v
+
+def get_nightly_date():
+  return datetime.datetime.today().strftime('%Y%m%d')
+
+def get_last_major():
+  return execute(['node', 'script/get-last-major-for-master.js'])
+
+def get_next_nightly(v):
+  pv = clean_parse_version(v)
+  major = pv[0]; minor = pv[1]; patch = pv[2]
+
+  if (is_stable(v)):
+    patch = str(int(pv[2]) + 1)
+
+  if execute(['git', 'rev-parse', '--abbrev-ref', 'HEAD']) == "master":
+    major = str(get_last_major() + 1)
+    minor = '0'
+    patch = '0'
+
+  pre = 'nightly.' + get_nightly_date()
+  return make_version(major, minor, patch, pre)
+
+def non_empty(thing):
+  return thing.strip() != ''
+
+def get_next_beta(v):
+  pv = clean_parse_version(v)
+  tag_pattern = 'v' + pv[0] + '.' + pv[1] + '.' + pv[2] + '-beta.*'
+  tag_list = filter(
+    non_empty,
+    execute(['git', 'tag', '--list', '-l', tag_pattern]).strip().split('\n')
+  )
+  if len(tag_list) == 0:
+    return make_version(pv[0] , pv[1],  pv[2], 'beta.1')
+
+  lv = parse_version(tag_list[-1])
+  return make_version(lv[0] , lv[1],  lv[2], str(int(lv[3]) + 1))
+
+def get_next_stable_from_pre(v):
+  pv = clean_parse_version(v)
+  major = pv[0]; minor = pv[1]; patch = pv[2]
+  return make_version(major, minor, patch)
+
+def get_next_stable_from_stable(v):
+  pv = clean_parse_version(v)
+  major = pv[0]; minor = pv[1]; patch = pv[2]
+  return make_version(major, minor, str(int(patch) + 1))
+
+def make_version(major, minor, patch, pre = None):
+  if pre is None:
+    return major + '.' + minor + '.' + patch
+  return major + "." + minor + "." + patch + '-' + pre

+ 29 - 15
script/prepare-release.js

@@ -1,10 +1,10 @@
 #!/usr/bin/env node
 
+if (!process.env.CI) require('dotenv-safe').load()
 require('colors')
 const args = require('minimist')(process.argv.slice(2), {
   boolean: ['automaticRelease', 'notesOnly', 'stable']
 })
-const assert = require('assert')
 const ciReleaseBuild = require('./ci-release-build')
 const { execSync } = require('child_process')
 const fail = '\u2717'.red
@@ -15,13 +15,13 @@ const path = require('path')
 const pkg = require('../package.json')
 const readline = require('readline')
 const versionType = args._[0]
+const targetRepo = versionType === 'nightly' ? 'nightlies' : 'electron'
 
 // TODO (future) automatically determine version based on conventional commits
 // via conventional-recommended-bump
 
-assert(process.env.ELECTRON_GITHUB_TOKEN, 'ELECTRON_GITHUB_TOKEN not found in environment')
 if (!versionType && !args.notesOnly) {
-  console.log(`Usage: prepare-release versionType [major | minor | patch | beta]` +
+  console.log(`Usage: prepare-release versionType [stable | beta | nightly]` +
      ` (--stable) (--notesOnly) (--automaticRelease) (--branch)`)
   process.exit(1)
 }
@@ -30,10 +30,13 @@ const github = new GitHub()
 const gitDir = path.resolve(__dirname, '..')
 github.authenticate({type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN})
 
-function getNewVersion (dryRun) {
+async function getNewVersion (dryRun) {
   console.log(`Bumping for new "${versionType}" version.`)
   let bumpScript = path.join(__dirname, 'bump-version.py')
-  let scriptArgs = [bumpScript, `--bump ${versionType}`]
+  let scriptArgs = [bumpScript]
+  if (args.bump) {
+    scriptArgs.push(`--bump ${versionType}`)
+  }
   if (args.stable) {
     scriptArgs.push('--stable')
   }
@@ -50,6 +53,7 @@ function getNewVersion (dryRun) {
     return newVersion
   } catch (err) {
     console.log(`${fail} Could not bump version, error was:`, err)
+    throw err
   }
 }
 
@@ -71,10 +75,13 @@ async function getCurrentBranch (gitDir) {
 }
 
 async function getReleaseNotes (currentBranch) {
+  if (versionType === 'nightly') {
+    return 'Nightlies do not get release notes, please compare tags for info'
+  }
   console.log(`Generating release notes for ${currentBranch}.`)
   let githubOpts = {
     owner: 'electron',
-    repo: 'electron',
+    repo: targetRepo,
     base: `v${pkg.version}`,
     head: currentBranch
   }
@@ -136,11 +143,11 @@ async function getReleaseNotes (currentBranch) {
 
 async function createRelease (branchToTarget, isBeta) {
   let releaseNotes = await getReleaseNotes(branchToTarget)
-  let newVersion = getNewVersion()
+  let newVersion = await getNewVersion()
   await tagRelease(newVersion)
   const githubOpts = {
     owner: 'electron',
-    repo: 'electron'
+    repo: targetRepo
   }
   console.log(`Checking for existing draft release.`)
   let releases = await github.repos.getReleases(githubOpts)
@@ -158,10 +165,17 @@ async function createRelease (branchToTarget, isBeta) {
   githubOpts.draft = true
   githubOpts.name = `electron ${newVersion}`
   if (isBeta) {
-    githubOpts.body = `Note: This is a beta release.  Please file new issues ` +
-      `for any bugs you find in it.\n \n This release is published to npm ` +
-      `under the beta tag and can be installed via npm install electron@beta, ` +
-      `or npm i electron@${newVersion.substr(1)}.\n \n ${releaseNotes}`
+    if (newVersion.indexOf('nightly') > 0) {
+      githubOpts.body = `Note: This is a nightly release.  Please file new issues ` +
+        `for any bugs you find in it.\n \n This release is published to npm ` +
+        `under the nightly tag and can be installed via npm install electron@nightly, ` +
+        `or npm i electron@${newVersion.substr(1)}.\n \n ${releaseNotes}`
+    } else {
+      githubOpts.body = `Note: This is a beta release.  Please file new issues ` +
+        `for any bugs you find in it.\n \n This release is published to npm ` +
+        `under the beta tag and can be installed via npm install electron@beta, ` +
+        `or npm i electron@${newVersion.substr(1)}.\n \n ${releaseNotes}`
+    }
     githubOpts.name = `${githubOpts.name}`
     githubOpts.prerelease = true
   } else {
@@ -209,7 +223,7 @@ async function tagRelease (version) {
 }
 
 async function verifyNewVersion () {
-  let newVersion = getNewVersion(true)
+  let newVersion = await getNewVersion(true)
   let response
   if (args.automaticRelease) {
     response = 'y'
@@ -239,8 +253,8 @@ async function promptForVersion (version) {
 
 async function prepareRelease (isBeta, notesOnly) {
   if (args.automaticRelease && (pkg.version.indexOf('beta') === -1 ||
-      versionType !== 'beta')) {
-    console.log(`${fail} Automatic release is only supported for beta releases`)
+      versionType !== 'beta') && versionType !== 'nightly' && versionType !== 'stable') {
+    console.log(`${fail} Automatic release is only supported for beta and nightly releases`)
     process.exit(1)
   }
   let currentBranch

+ 12 - 3
script/publish-to-npm.js

@@ -7,6 +7,11 @@ const request = require('request')
 const assert = require('assert')
 const rootPackageJson = require('../package.json')
 
+if (!process.env.ELECTRON_NPM_OTP) {
+  console.error('Please set ELECTRON_NPM_OTP')
+  process.exit(1)
+}
+
 const github = new GitHubApi({
   // debug: true,
   headers: { 'User-Agent': 'electron-npm-publisher' },
@@ -68,7 +73,7 @@ new Promise((resolve, reject) => {
 
   return github.repos.getReleases({
     owner: 'electron',
-    repo: 'electron'
+    repo: rootPackageJson.version.indexOf('nightly') > 0 ? 'nightlies' : 'electron'
   })
 })
 .then((releases) => {
@@ -104,7 +109,11 @@ new Promise((resolve, reject) => {
   })
 })
 .then((release) => {
-  npmTag = release.prerelease ? 'beta' : 'latest'
+  if (release.tag_name.indexOf('nightly') > 0) {
+    npmTag = 'nightly'
+  } else {
+    npmTag = release.prerelease ? 'beta' : 'latest'
+  }
 })
 .then(() => childProcess.execSync('npm pack', { cwd: tempDir }))
 .then(() => {
@@ -120,7 +129,7 @@ new Promise((resolve, reject) => {
     resolve(tarballPath)
   })
 })
-.then((tarballPath) => childProcess.execSync(`npm publish ${tarballPath} --tag ${npmTag}`))
+.then((tarballPath) => childProcess.execSync(`npm publish ${tarballPath} --tag ${npmTag} --otp=${process.env.ELECTRON_NPM_OTP}`))
 .catch((err) => {
   console.error(`Error: ${err}`)
   process.exit(1)

+ 11 - 15
script/release.js

@@ -1,8 +1,8 @@
 #!/usr/bin/env node
 
+if (!process.env.CI) require('dotenv-safe').load()
 require('colors')
 const args = require('minimist')(process.argv.slice(2))
-const assert = require('assert')
 const fs = require('fs')
 const { execSync } = require('child_process')
 const GitHub = require('github')
@@ -16,17 +16,16 @@ const fail = '\u2717'.red
 const sumchecker = require('sumchecker')
 const temp = require('temp').track()
 const { URL } = require('url')
+const targetRepo = pkgVersion.indexOf('nightly') > 0 ? 'nightlies' : 'electron'
 let failureCount = 0
 
-assert(process.env.ELECTRON_GITHUB_TOKEN, 'ELECTRON_GITHUB_TOKEN not found in environment')
-
 const github = new GitHub({
   followRedirects: false
 })
 github.authenticate({type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN})
 
 async function getDraftRelease (version, skipValidation) {
-  let releaseInfo = await github.repos.getReleases({owner: 'electron', repo: 'electron'})
+  let releaseInfo = await github.repos.getReleases({owner: 'electron', repo: targetRepo})
   let drafts
   let versionToCheck
   if (version) {
@@ -96,7 +95,6 @@ function assetsForVersion (version, validatingRelease) {
     `electron-${version}-linux-armv7l.zip`,
     `electron-${version}-linux-ia32-symbols.zip`,
     `electron-${version}-linux-ia32.zip`,
-//    `electron-${version}-linux-mips64el.zip`,
     `electron-${version}-linux-x64-symbols.zip`,
     `electron-${version}-linux-x64.zip`,
     `electron-${version}-mas-x64-dsym.zip`,
@@ -114,7 +112,6 @@ function assetsForVersion (version, validatingRelease) {
     `ffmpeg-${version}-linux-arm64.zip`,
     `ffmpeg-${version}-linux-armv7l.zip`,
     `ffmpeg-${version}-linux-ia32.zip`,
-//    `ffmpeg-${version}-linux-mips64el.zip`,
     `ffmpeg-${version}-linux-x64.zip`,
     `ffmpeg-${version}-mas-x64.zip`,
     `ffmpeg-${version}-win32-ia32.zip`,
@@ -144,6 +141,8 @@ function s3UrlsForVersion (version) {
 }
 
 function checkVersion () {
+  if (args.skipVersionCheck) return
+
   console.log(`Verifying that app version matches package version ${pkgVersion}.`)
   let startScript = path.join(__dirname, 'start.py')
   let scriptArgs = ['--version']
@@ -181,11 +180,7 @@ function uploadNodeShasums () {
 function uploadIndexJson () {
   console.log('Uploading index.json to S3.')
   let scriptPath = path.join(__dirname, 'upload-index-json.py')
-  let scriptArgs = []
-  if (args.automaticRelease) {
-    scriptArgs.push('-R')
-  }
-  runScript(scriptPath, scriptArgs)
+  runScript(scriptPath, [pkgVersion])
   console.log(`${pass} Done uploading index.json to S3.`)
 }
 
@@ -196,7 +191,7 @@ async function createReleaseShasums (release) {
     console.log(`${fileName} already exists on GitHub; deleting before creating new file.`)
     await github.repos.deleteAsset({
       owner: 'electron',
-      repo: 'electron',
+      repo: targetRepo,
       id: existingAssets[0].id
     }).catch(err => {
       console.log(`${fail} Error deleting ${fileName} on GitHub:`, err)
@@ -215,7 +210,7 @@ async function createReleaseShasums (release) {
 async function uploadShasumFile (filePath, fileName, release) {
   let githubOpts = {
     owner: 'electron',
-    repo: 'electron',
+    repo: targetRepo,
     id: release.id,
     filePath,
     name: fileName
@@ -250,7 +245,7 @@ function saveShaSumFile (checksums, fileName) {
 async function publishRelease (release) {
   let githubOpts = {
     owner: 'electron',
-    repo: 'electron',
+    repo: targetRepo,
     id: release.id,
     tag_name: release.tag_name,
     draft: false
@@ -277,6 +272,7 @@ async function makeRelease (releaseToValidate) {
     let draftRelease = await getDraftRelease()
     uploadNodeShasums()
     uploadIndexJson()
+
     await createReleaseShasums(draftRelease)
     // Fetch latest version of release before verifying
     draftRelease = await getDraftRelease(pkgVersion, true)
@@ -304,7 +300,7 @@ async function verifyAssets (release) {
   let downloadDir = await makeTempDir()
   let githubOpts = {
     owner: 'electron',
-    repo: 'electron',
+    repo: targetRepo,
     headers: {
       Accept: 'application/octet-stream'
     }

+ 9 - 2
script/run-clang-format.py

@@ -19,6 +19,7 @@ import signal
 import subprocess
 import sys
 import traceback
+import tempfile
 
 from functools import partial
 
@@ -68,8 +69,8 @@ def make_diff(diff_file, original, reformatted):
         difflib.unified_diff(
             original,
             reformatted,
-            fromfile='{}\t(original)'.format(diff_file),
-            tofile='{}\t(reformatted)'.format(diff_file),
+            fromfile='a/{}'.format(diff_file),
+            tofile='b/{}'.format(diff_file),
             n=3))
 
 
@@ -312,6 +313,12 @@ def main():
                 continue
             if not args.quiet:
                 print_diff(outs, use_color=colored_stdout)
+                with tempfile.NamedTemporaryFile(delete=False) as patch_file:
+                    for line in outs:
+                        patch_file.write(line)
+                    patch_file.write('\n')
+                    print("\nTo apply this patch, run:\n$ git apply {}\n"
+                          .format(patch_file.name))
             if retcode == ExitStatus.SUCCESS:
                 retcode = ExitStatus.DIFF
     return retcode

+ 5 - 0
script/test.py

@@ -18,6 +18,9 @@ if sys.platform == 'linux2':
     # will be picked up by electron.
     try:
         import lib.dbus_mock
+        import atexit
+        lib.dbus_mock.start()
+        atexit.register(lib.dbus_mock.stop)
     except ImportError:
         # If not available, the powerMonitor tests will be skipped since
         # DBUS_SYSTEM_BUS_ADDRESS will not be set
@@ -55,6 +58,8 @@ def main():
     electron = os.path.join(SOURCE_ROOT, 'out', config,
                               '{0}.exe'.format(PROJECT_NAME))
     resources_path = os.path.join(SOURCE_ROOT, 'out', config)
+    if config != 'R':
+      os.environ['ELECTRON_SKIP_NATIVE_MODULE_TESTS'] = '1'
   else:
     electron = os.path.join(SOURCE_ROOT, 'out', config, PROJECT_NAME)
     resources_path = os.path.join(SOURCE_ROOT, 'out', config)

+ 21 - 15
script/upload-index-json.py

@@ -2,32 +2,38 @@
 
 import os
 import sys
+import urllib2
 
-from lib.config import PLATFORM, s3_config
-from lib.util import electron_gyp, execute, s3put, scoped_cwd
-
+from lib.config import s3_config
+from lib.util import s3put, scoped_cwd, safe_mkdir
 
 SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
 OUT_DIR     = os.path.join(SOURCE_ROOT, 'out', 'D')
 
-PROJECT_NAME = electron_gyp()['project_name%']
-PRODUCT_NAME = electron_gyp()['product_name%']
+BASE_URL = 'https://electron-metadumper.herokuapp.com/?version=v'
 
+version = sys.argv[1]
+authToken = os.getenv('META_DUMPER_AUTH_HEADER')
 
 def main():
+  if not authToken or authToken == "":
+    raise Exception("Please set META_DUMPER_AUTH_HEADER")
   # Upload the index.json.
   with scoped_cwd(SOURCE_ROOT):
-    if sys.platform == 'darwin':
-      electron = os.path.join(OUT_DIR, '{0}.app'.format(PRODUCT_NAME),
-                                'Contents', 'MacOS', PRODUCT_NAME)
-    elif sys.platform == 'win32':
-      electron = os.path.join(OUT_DIR, '{0}.exe'.format(PROJECT_NAME))
-    else:
-      electron = os.path.join(OUT_DIR, PROJECT_NAME)
+    safe_mkdir(OUT_DIR)
     index_json = os.path.relpath(os.path.join(OUT_DIR, 'index.json'))
-    execute([electron,
-             os.path.join('tools', 'dump-version-info.js'),
-             index_json])
+
+    request = urllib2.Request(
+      BASE_URL + version,
+      headers={"Authorization" : authToken}
+    )
+
+    new_content = urllib2.urlopen(
+      request
+    ).read()
+
+    with open(index_json, "w") as f:
+      f.write(new_content)
 
     bucket, access_key, secret_key = s3_config()
     s3put(bucket, access_key, secret_key, OUT_DIR, 'atom-shell/dist',

+ 8 - 3
script/upload-to-github.js

@@ -1,18 +1,23 @@
+if (!process.env.CI) require('dotenv-safe').load()
+
 const GitHub = require('github')
 const github = new GitHub()
 github.authenticate({type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN})
 
-if (process.argv.length < 5) {
+if (process.argv.length < 6) {
   console.log('Usage: upload-to-github filePath fileName releaseId')
   process.exit(1)
 }
 let filePath = process.argv[2]
 let fileName = process.argv[3]
 let releaseId = process.argv[4]
+let releaseVersion = process.argv[5]
+
+const targetRepo = releaseVersion.indexOf('nightly') > 0 ? 'nightlies' : 'electron'
 
 let githubOpts = {
   owner: 'electron',
-  repo: 'electron',
+  repo: targetRepo,
   id: releaseId,
   filePath: filePath,
   name: fileName
@@ -34,7 +39,7 @@ function uploadToGitHub () {
           console.log(`${fileName} already exists; will delete before retrying upload.`)
           github.repos.deleteAsset({
             owner: 'electron',
-            repo: 'electron',
+            repo: targetRepo,
             id: existingAssets[0].id
           }).then(uploadToGitHub).catch(uploadToGitHub)
         } else {

+ 5 - 4
script/upload.py

@@ -37,7 +37,7 @@ def main():
   args = parse_args()
   if  args.upload_to_s3:
     utcnow = datetime.datetime.utcnow()
-    args.upload_timestamp = utcnow.strftime('%Y-%m-%d_%H:%M:%S')
+    args.upload_timestamp = utcnow.strftime('%Y%m%d')
 
   if not dist_newer_than_head():
     run_python_script('create-dist.py')
@@ -165,17 +165,18 @@ def upload_electron(release, file_path, args):
     return
 
   # Upload the file.
-  upload_io_to_github(release, filename, file_path)
+  upload_io_to_github(release, filename, file_path, args.version)
 
   # Upload the checksum file.
   upload_sha256_checksum(args.version, file_path)
 
 
-def upload_io_to_github(release, filename, filepath):
+def upload_io_to_github(release, filename, filepath, version):
   print 'Uploading %s to Github' % \
       (filename)
   script_path = os.path.join(SOURCE_ROOT, 'script', 'upload-to-github.js')
-  execute(['node', script_path, filepath, filename, str(release['id'])])
+  execute(['node', script_path, filepath, filename, str(release['id']),
+          version])
 
 
 def upload_sha256_checksum(version, file_path, key_prefix=None):