diff --git a/build/lib/build.js b/build/lib/build.js index a6b78a5f3398..3a26a47a9d4e 100644 --- a/build/lib/build.js +++ b/build/lib/build.js @@ -80,10 +80,8 @@ const checkVersionsMatch = () => { const found = versionData.match(re) const braveVersionFromChromeFile = `${found[2]}.${found[3]}.${found[4]}` if (braveVersionFromChromeFile !== config.braveVersion) { + // Only a warning. The CI environment will choose to proceed or not within its own script. console.warn(`Version files do not match!\nsrc/chrome/VERSION: ${braveVersionFromChromeFile}\nbrave-browser package.json version: ${config.braveVersion}`) - if (config.buildConfig === 'Release') { - process.exit(1) - } } } @@ -96,9 +94,24 @@ const build = (buildConfig = config.defaultBuildConfig, options) => { touchOverriddenVectorIconFiles() util.updateBranding() - util.buildTarget() - if (config.shouldSign()) { - util.signApp() + if (config.xcode_gen_target) { + util.generateXcodeWorkspace() + } else { + util.buildTarget() + if (config.shouldSign()) { + util.signApp() + } + + if (process.platform === 'win32') { + // Sign only binaries for widevine sig generation. + // Other binaries will be done during the create_dist. + // Then, both are merged whenarchive for installer is created. + util.signWinBinaries() + + if (config.brave_enable_cdm_host_verification) { + util.generateWidevineSigFiles() + } + } } } diff --git a/build/lib/calculateFileChecksum.js b/build/lib/calculateFileChecksum.js new file mode 100644 index 000000000000..253f688aad0d --- /dev/null +++ b/build/lib/calculateFileChecksum.js @@ -0,0 +1,28 @@ +// Copyright (c) 2019 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +const crypto = require('crypto') +const fs = require('fs') + +module.exports = function CalculateFileChecksum(filePath, algorithm = 'sha256') { + return new Promise((resolve, reject) => { + try { + const checksumGenerator = crypto.createHash(algorithm); + const fileStream = fs.createReadStream(filePath) + fileStream.on('error', function (err) { + err.message = `CalculateFileChecksum error in FileStream at path "${filePath}": ${err.message}` + reject(err) + }) + checksumGenerator.once('readable', function () { + const checksum = checksumGenerator.read().toString('hex') + resolve(checksum) + }) + fileStream.pipe(checksumGenerator) + } catch (err) { + err.message = `CalculateFileChecksum error using algorithm "${algorithm}" at path "${filePath}": ${err.message}` + reject(err); + } + }); +} diff --git a/build/lib/calculateFileChecksum.test.js b/build/lib/calculateFileChecksum.test.js new file mode 100644 index 000000000000..c63eb35cb0b8 --- /dev/null +++ b/build/lib/calculateFileChecksum.test.js @@ -0,0 +1,45 @@ +const path = require('path') +const fs = require('fs-extra') +const os = require('os') +const calculateFileChecksum = require('./calculateFileChecksum') + +const dirPrefixTmp = 'brave-browser-test-calculate-file-checksum-' +const testFile1Name = 'file1' +const testFile1InitialContent = 'this is a test' +const encoding = 'utf8' +let testDirPath, testFile1Path + +beforeEach(async function () { + // Test directory + testDirPath = await fs.mkdtemp(path.join(os.tmpdir(), dirPrefixTmp)) + // Initial test file + testFile1Path = path.join(testDirPath, testFile1Name) + await fs.writeFile(testFile1Path, testFile1InitialContent, encoding) +}) + +afterEach(async function () { + // Remove test directory + try { + await fs.remove(testDirPath) + } catch (err) { + console.warn('Test cleanup: could not remove temp directory at ' + testDirPath) + } +}) + +test('generates a checksum', async function () { + const checksum1 = await calculateFileChecksum(testFile1Path) + expect(checksum1).toBeTruthy() +}) + +test('checksum is stable', async function () { + const checksum1 = await calculateFileChecksum(testFile1Path) + const checksum2 = await calculateFileChecksum(testFile1Path) + expect(checksum2).toBe(checksum1) +}) + +test('checksum changes when file contents change', async function () { + const checksum1 = await calculateFileChecksum(testFile1Path) + await fs.writeFile(testFile1Path, testFile1InitialContent + testFile1InitialContent, encoding) + const checksum2 = await calculateFileChecksum(testFile1Path) + expect(checksum2).not.toBe(checksum1) +}) diff --git a/build/lib/config.js b/build/lib/config.js old mode 100644 new mode 100755 index b0de942ea098..7082560093b1 --- a/build/lib/config.js +++ b/build/lib/config.js @@ -2,6 +2,7 @@ const path = require('path') const fs = require('fs') +const assert = require('assert') const packages = require('../package') @@ -15,6 +16,19 @@ const getNPMConfig = (path) => { process.env[package_prefix + key] } +const parseExtraInputs = (inputs, accumulator, callback) => { + for (let input of inputs) { + let separatorIndex = input.indexOf(':') + if (separatorIndex < 0) { + separatorIndex = input.length + } + + const key = input.substring(0, separatorIndex); + const value = input.substring(separatorIndex + 1); + callback(accumulator, key, value) + } +} + const Config = function () { this.defaultBuildConfig = 'Debug' this.buildConfig = this.defaultBuildConfig @@ -33,11 +47,13 @@ const Config = function () { this.gClientVerbose = getNPMConfig(['gclient_verbose']) || false this.targetArch = 'x64' this.gypTargetArch = 'x64' + this.targetApkBase ='classic' this.officialBuild = true this.debugBuild = JSON.parse(getNPMConfig(['brave_debug_build']) || false) this.braveGoogleApiKey = getNPMConfig(['brave_google_api_key']) || 'AIzaSyAQfxPJiounkhOjODEO5ZieffeBv6yft2Q' this.googleApiKey = getNPMConfig(['google_api_key']) || 'AIzaSyAH90V94EcZBP5oH7oc-mXQrSKgASVxER8' this.googleApiEndpoint = getNPMConfig(['brave_google_api_endpoint']) || 'https://www.googleapis.com/geolocation/v1/geolocate?key=' + this.infuraProjectId = getNPMConfig(['brave_infura_project_id']) || '' this.safeBrowsingApiEndpoint = getNPMConfig(['safe_browsing_api_endpoint']) || 'safebrowsing.brave.com' this.buildProjects() this.braveVersion = getNPMConfig(['version']) || '0.0.0.0' @@ -53,6 +69,13 @@ const Config = function () { this.ignore_compile_failure = false this.enable_hangout_services_extension = true this.widevineVersion = getNPMConfig(['widevine', 'version']) + this.brave_enable_cdm_host_verification = false + this.sign_widevine_cert = process.env.SIGN_WIDEVINE_CERT || '' + this.sign_widevine_key = process.env.SIGN_WIDEVINE_KEY || '' + this.sign_widevine_passwd = process.env.SIGN_WIDEVINE_PASSPHRASE || '' + this.signature_generator = path.join(this.srcDir, 'third_party', 'widevine', 'scripts', 'signature_generator.py') || '' + this.extraGnArgs = {} + this.extraNinjaOpts = [] } Config.prototype.buildArgs = function () { @@ -63,6 +86,7 @@ Config.prototype.buildArgs = function () { const chrome_version_parts = this.chromeVersion.split('.') let args = { + fieldtrial_testing_like_official_build: true, safe_browsing_mode: 1, root_extra_deps: ["//brave"], // TODO: Re-enable when chromium_src overrides work for files in relative @@ -75,6 +99,7 @@ Config.prototype.buildArgs = function () { // branding_path_component: "brave", enable_widevine: true, target_cpu: this.targetArch, + target_apk_base: this.targetApkBase, is_official_build: this.officialBuild, is_debug: this.buildConfig !== 'Release', dcheck_always_on: this.buildConfig !== 'Release', @@ -82,6 +107,7 @@ Config.prototype.buildArgs = function () { google_api_key: this.googleApiKey, brave_google_api_key: this.braveGoogleApiKey, brave_google_api_endpoint: this.googleApiEndpoint, + brave_infura_project_id: this.infuraProjectId, brave_product_name: getNPMConfig(['brave_product_name']) || "brave-core", brave_project_name: getNPMConfig(['brave_project_name']) || "brave-core", brave_version_major: version_parts[0], @@ -91,10 +117,12 @@ Config.prototype.buildArgs = function () { chrome_version_major: chrome_version_parts[0], safebrowsing_api_endpoint: this.safeBrowsingApiEndpoint, brave_referrals_api_key: this.braveReferralsApiKey, - enable_hangout_services_extension: this.enable_hangout_services_extension + enable_hangout_services_extension: this.enable_hangout_services_extension, + enable_cdm_host_verification: this.brave_enable_cdm_host_verification, + ...this.extraGnArgs, } - if (process.platform === 'darwin') { + if (process.platform === 'darwin' && this.targetOS !== 'ios') { args.mac_signing_identifier = this.mac_signing_identifier args.mac_installer_signing_identifier = this.mac_installer_signing_identifier args.mac_signing_keychain = this.mac_signing_keychain @@ -110,7 +138,7 @@ Config.prototype.buildArgs = function () { args.skip_signing = true } - if (this.debugBuild) { + if (this.debugBuild && this.targetOS !== 'ios') { if (process.platform === 'darwin') { args.enable_stripping = false } @@ -135,6 +163,56 @@ Config.prototype.buildArgs = function () { args.use_vaapi = true } + if (this.targetOS === 'android') { + args.target_os = 'android' + if (!this.officialBuild) { + args.manifest_package = 'com.brave.browser_default' + } else { + args.manifest_package = 'com.brave.browser' + } + + // TODO(fixme) + args.brave_rewards_enabled = false + args.brave_ads_enabled = false + args.enable_tor = false + args.enable_brave_sync = false + + // These do not exist on android + // TODO - recheck + delete args.safe_browsing_mode + delete args.proprietary_codecs + delete args.ffmpeg_branding + delete args.enable_nacl + delete args.branding_path_component + delete args.enable_widevine + delete args.enable_hangout_services_extension + delete args.brave_infura_project_id + } + + if (this.targetOS === 'ios') { + args.target_os = 'ios' + args.enable_dsyms = false + args.enable_stripping = args.enable_dsyms + args.use_xcode_clang = args.is_official_build + args.use_clang_coverage = false + args.is_component_build = false + args.ios_deployment_target = '12.0' + args.ios_enable_code_signing = false + + delete args.safebrowsing_api_endpoint + delete args.safe_browsing_mode + delete args.proprietary_codecs + delete args.ffmpeg_branding + delete args.enable_nacl + delete args.branding_path_component + delete args.enable_widevine + delete args.enable_hangout_services_extension + delete args.brave_google_api_endpoint + delete args.brave_google_api_key + delete args.brave_referrals_api_key + delete args.brave_infura_project_id + } + if (process.platform === 'win32') { args.cc_wrapper = path.join(this.srcDir, 'brave', 'script', 'redirect-cc.cmd') } else { @@ -146,7 +224,10 @@ Config.prototype.buildArgs = function () { Config.prototype.shouldSign = function () { // it doesn't make sense to sign debug builds because the restrictions on loading // dynamic libs prevents them from working anyway - return this.mac_signing_identifier !== '' && this.buildConfig === 'Release' + return this.mac_signing_identifier !== '' && + !this.skip_signing && + this.buildConfig === 'Release' && + this.targetOS !== 'ios' } Config.prototype.prependPath = function (oldPath, addPath) { @@ -233,19 +314,30 @@ Config.prototype.buildProjects = function () { } Config.prototype.update = function (options) { - if (options.C) { - this.buildConfig = path.basename(options.C) - this.__outputDir = options.C - } - if (options.target_arch === 'x86') { this.targetArch = options.target_arch this.gypTargetArch = 'ia32' - } - - if (options.target_arch === 'ia32') { + } else if (options.target_arch === 'ia32') { this.targetArch = 'x86' this.gypTargetArch = options.target_arch + } else if (options.target_arch) { + this.targetArch = options.target_arch + } + + if (options.target_os === 'android') { + this.targetOS = 'android' + if (options.target_apk_base) { + this.targetApkBase = options.target_apk_base + } + } + + if (options.target_os) { + this.targetOS = options.target_os + } + + if (options.C) { + this.buildConfig = path.basename(options.C) + this.__outputDir = options.C } if (options.gclient_file && options.gclient_file !== 'default') { @@ -260,6 +352,10 @@ Config.prototype.update = function (options) { this.googleApiEndpoint = options.brave_google_api_endpoint } + if (options.brave_infura_project_id) { + this.infuraProjectId = options.infura_project_id + } + if (options.safebrowsing_api_endpoint) { this.safeBrowsingApiEndpoint = options.safebrowsing_api_endpoint } @@ -290,6 +386,18 @@ Config.prototype.update = function (options) { this.channel = options.channel } + if (this.buildConfig === 'Release' && process.platform !== 'linux') { + this.brave_enable_cdm_host_verification = + this.sign_widevine_cert !== "" && this.sign_widevine_key !== "" && + this.sign_widevine_passwd !== "" && fs.existsSync(this.signature_generator) + + if (this.brave_enable_cdm_host_verification) { + console.log('Widevine cdm host verification is enabled') + } else { + console.log('Widevine cdm host verification is disabled') + } + } + if (process.platform === 'win32' && options.build_omaha) { this.build_omaha = true this.tag_ap = options.tag_ap @@ -314,6 +422,33 @@ Config.prototype.update = function (options) { if (options.ignore_compile_failure) this.ignore_compile_failure = true + if (options.xcode_gen) { + assert(process.platform === 'darwin' || options.target_os === 'ios') + if (options.xcode_gen === 'ios') { + this.xcode_gen_target = '//brave/vendor/brave-ios:*' + } else { + this.xcode_gen_target = options.xcode_gen + } + } + + if (options.gn) { + parseExtraInputs(options.gn, this.extraGnArgs, (args, key, value) => { + try { + value = JSON.parse(value) + } catch (e) { + // On parse error, leave value as string. + } + args[key] = value + }) + } + + if (options.ninja) { + parseExtraInputs(options.ninja, this.extraNinjaOpts, (opts, key, value) => { + opts.push(`-${key}`) + opts.push(value) + }) + } + this.projectNames.forEach((projectName) => { // don't update refs for projects that have them let project = this.projects[projectName] @@ -344,6 +479,15 @@ Object.defineProperty(Config.prototype, 'defaultOptions', { if (this.sccache) { env.CC_WRAPPER = this.sccache + if (path.basename(this.sccache) === 'ccache') { + console.log('using ccache') + env.CCACHE_CPP2 = 'yes' + env.CCACHE_SLOPPINESS = 'pch_defines,time_macros,include_file_mtime' + env.CCACHE_BASEDIR = this.srcDir + env = this.addPathToEnv(env, path.join(this.srcDir, 'third_party', 'llvm-build', 'Release+Asserts', 'bin')) + } else { + console.log('using sccache') + } } if (process.platform === 'linux') { @@ -370,8 +514,15 @@ Object.defineProperty(Config.prototype, 'outputDir', { if (this.__outputDir) return this.__outputDir let baseDir = path.join(this.srcDir, 'out') - baseDir = this.targetArch == 'x86' ? baseDir + '_x86' : baseDir - return path.join(baseDir, this.buildConfig) + let buildConfigDir = this.buildConfig + if (this.targetArch && this.targetArch != 'x64') { + buildConfigDir = buildConfigDir + '_' + this.targetArch + } + if (this.targetOS) { + buildConfigDir = this.targetOS + "_" + buildConfigDir + } + + return path.join(baseDir, buildConfigDir) }, set: function (outputDir) { return this.__outputDir = outputDir }, }) diff --git a/build/lib/gitPatcher.js b/build/lib/gitPatcher.js new file mode 100644 index 000000000000..f735a9341767 --- /dev/null +++ b/build/lib/gitPatcher.js @@ -0,0 +1,325 @@ +// Copyright (c) 2019 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +const path = require('path') +const fs = require('fs-extra') +const os = require('os') +const util = require('../lib/util') +const calculateFileChecksum = require('../lib/calculateFileChecksum') + +const extPatch = 'patch' +const extPatchInfo = 'patchinfo' +const encodingPatchInfo = 'utf8' +// Increment schema version if we make breaking changes +// to the Patch Info file format. +const patchInfoSchemaVersion = 1 +const applyArgs = [ '--ignore-space-change', '--ignore-whitespace' ] + +const patchApplyReasons = { + NO_PATCH_INFO: 0, + PATCH_INFO_OUTDATED: 1, + PATCH_CHANGED: 2, + PATCH_REMOVED: 3, + SRC_CHANGED: 4 +} + +const patchApplyReasonMessages = [ + `No corresponding .${extPatchInfo} file was found.`, + `The corresponding .${extPatchInfo} file was unreadable or not in the correct schema version of ${patchInfoSchemaVersion}.`, + `The .${extPatch} file was modified since last applied.`, + `The .${extPatch} file was removed since last applied.`, + `The target file was modified since the patch was last applied.` +] + +// Intrepret `--numstat -z` line format +// https://regex101.com/r/jP1JEP/1 +const regexGitApplyNumStats = /^((\d|-)+\s+){2}/ + +module.exports = class GitPatcher { + constructor (patchDirPath, repoPath, logProgress = true) { + this.patchDirPath = patchDirPath + this.repoPath = repoPath + this.shouldLogProgress = logProgress + } + + logProgressLine(...messages) { + if (this.shouldLogProgress) { + console.log(...messages) + } + } + + logProgress(message) { + if (this.shouldLogProgress) { + process.stdout.write(message) + } + } + + async applyPatches () { + // STRATEGY: + // 1. iterate .patch files in dir + // corresponding .patchinfo file? + // - no? add to TO_PATCH list + // - yes? check hash of patch file and each chromium file. different? add to TOPATCH list. + // 2. iterate .patchinfo files in dir + // corresponding .patch file? + // - no? add to TO_RESET list + // 3. iterate TO_PATCH list + // - reset chromium file + // - apply patch + // - create .patchinfo file + // 4. iterate TO_RESET list + // - reset chromium file + // - delete .patchinfo file + + const [patchDirExists, repoDirExists] = await Promise.all([ + fs.exists(this.patchDirPath), + fs.exists(this.repoPath) + ]) + if (!patchDirExists) { + return [] + } + if (!repoDirExists) { + throw new Error(`Could not apply patches. Repo at path "${this.repoPath}" does not exist.`) + } + const allFilenames = await fs.readdir(this.patchDirPath) + const patchFilenames = allFilenames.filter(s => s.endsWith(`.${extPatch}`)) + const patchInfoFilenames = allFilenames.filter(s => s.endsWith(`.${extPatchInfo}`)) + + const patchesToApply = [] + const patchInfosObsolete = [] + + for (const filename of patchFilenames) { + const patchInfoFilename = filename.slice(0, extPatch.length * -1) + extPatchInfo + const hasPatchInfo = patchInfoFilenames.includes(patchInfoFilename) + const fullPath = path.join(this.patchDirPath, filename) + const patchInfoFullPath = path.join(this.patchDirPath, patchInfoFilename) + const needsPatchReason = (!hasPatchInfo) + ? patchApplyReasons.NO_PATCH_INFO + : (await this.isPatchStale(fullPath, patchInfoFullPath)) + if (needsPatchReason !== null) { + patchesToApply.push({ + patchPath: fullPath, + patchInfoPath: path.join(this.patchDirPath, patchInfoFilename), + reason: needsPatchReason + }) + } + } + + for (const filename of patchInfoFilenames) { + const patchFilename = filename.slice(0, extPatchInfo.length * -1) + extPatch + const hasPatch = patchFilenames.includes(patchFilename) + if (!hasPatch) { + const fullPath = path.join(this.patchDirPath, filename) + patchInfosObsolete.push(fullPath) + } + } + const pathStatuses = [] + try { + if (patchesToApply.length) { + const appliedPathsStatuses = await this.performApplyForPatches(patchesToApply) + pathStatuses.push(...appliedPathsStatuses) + } + if (patchInfosObsolete.length) { + const resetStatuses = await this.handleObsoletePatchInfos(patchInfosObsolete) + pathStatuses.push(...resetStatuses) + } + } catch (err) { + console.error(err) + console.error('There was an error applying added, modified or removed patches. Please consider running `init` to reset and re-apply all patches.') + } + return pathStatuses + } + + async getPatchInfo (patchInfoPath) { + try { + const patchInfoRaw = await fs.readFile(patchInfoPath, encodingPatchInfo) + const patchInfo = JSON.parse(patchInfoRaw) + return patchInfo + } catch (err) { + err.message = `Error reading Patch Info file at path "${patchInfoPath}": ${err.message}` + throw err + } + } + + async isPatchStale (patchPath, patchInfoPath) { + const patchInfo = await this.getPatchInfo(patchInfoPath) + // Validate + // Always stale if schema has changed + // Always stale if invalid file + if (!patchInfo || patchInfo.schemaVersion !== patchInfoSchemaVersion) { + return patchApplyReasons.PATCH_INFO_OUTDATED + } + const { patchChecksum, appliesTo } = patchInfo + // Detect if patch file changed since patch was applied + const currentPatchChecksum = await calculateFileChecksum(patchPath) + if (currentPatchChecksum !== patchChecksum) { + return patchApplyReasons.PATCH_CHANGED + } + // Detect if any of the files the patch applies to have changed + for (const {path: localPath, checksum} of appliesTo) { + const fullPath = path.join(this.repoPath, localPath) + const currentChecksum = await calculateFileChecksum(fullPath) + if (currentChecksum !== checksum) { + return patchApplyReasons.SRC_CHANGED + } + } + // Nothing was changed + return null + } + + async performApplyForPatches (patchesToApply) { + // The actual apply cannot be done in parallel with other write ops, + // but everything else can. + // First, find out which files the patch applies to, so we know + // which files to reset. + const prepOps = [] + this.logProgress(os.EOL + 'Getting patch data...') + for (const patchData of patchesToApply) { + prepOps.push( + this.getAppliesTo(patchData.patchPath) + .then((appliesTo) => ({ + appliesTo, + ...patchData + })) + .catch((err) => ({ + error: new Error('Could not read data from patch file: ' + err.message), + ...patchData + })) + .then((data) => { + this.logProgress('.') + return data + }) + ) + } + + const patchSets = await Promise.all(prepOps) + this.logProgress(os.EOL + 'Resetting...') + // Reset all repo files + const allRepoPaths = patchSets.filter(p => !p.error).reduce( + (allPaths, set) => allPaths.concat(set.appliesTo.map(s => s.path)), + [] + ) + try { + await this.resetRepoFiles(allRepoPaths) + } catch { + console.warn('There were some failures during git reset of specific repo paths: ', allRepoPaths.join(' ')) + } + this.logProgressLine('done.') + this.logProgress('Applying patches:') + // Apply patches (in series) + for (const patchData of patchSets) { + const { patchPath } = patchData + this.logProgress('.') + try { + await util.runGitAsync(this.repoPath, ['apply', patchPath, ...applyArgs]) + } catch (err) { + patchData.error = err + } + } + this.logProgressLine('All patch apply done.') + // Create Patch Info file using post-patch repo file cheksums + // (in parallel) + const patchInfoOps = [] + for (const { appliesTo, patchPath, patchInfoPath } of patchSets.filter(p => !p.error)) { + patchInfoOps.push(this.writePatchInfo(patchInfoPath, appliesTo, patchPath)) + } + + await Promise.all(patchInfoOps) + + // Provide status to caller + return patchSets.reduce( + (all, { appliesTo, patchPath, error, reason }) => { + if (appliesTo && appliesTo.length) { + return all.concat(appliesTo.map( + ({ path }) => ({ + path, + patchPath, + error, + reason + }) + )) + } else { + return all.concat([{ + patchPath, + error, + reason + }]) + } + }, + [] + ) + } + + async getAppliesTo (patchPath) { + const applyStatArgs = ['apply', patchPath, '--numstat', '-z', ...applyArgs] + // Check which files patch applies to + return ( await util.runGitAsync(this.repoPath, applyStatArgs) ) + .split(os.EOL) + .filter(s => s) + // Intrepret `--numstat -z` line format + .map(s => ({ + path: s.replace(regexGitApplyNumStats, '').replace(/\0/g, '') + })) + } + + async writePatchInfo (patchInfoPath, appliesTo, patchPath) { + for (const appliesToFile of appliesTo) { + appliesToFile.checksum = await calculateFileChecksum(path.join(this.repoPath, appliesToFile.path)) + } + const patchInfo = { + schemaVersion: patchInfoSchemaVersion, + patchChecksum: await calculateFileChecksum(patchPath), + appliesTo + } + await fs.writeFile(patchInfoPath, JSON.stringify(patchInfo), { encoding: encodingPatchInfo }) + } + + resetRepoFiles (filePaths) { + return util.runGitAsync(this.repoPath, ['checkout', ...filePaths]) + } + + async handleObsoletePatchInfos (patchInfosObsolete) { + const ops = [] + const allPaths = [] + const allStatuses = [] + for (const patchInfoPath of patchInfosObsolete) { + const patchInfo = await this.getPatchInfo(patchInfoPath) + // remove patchinfo file + const removeOp = fs.unlink(patchInfoPath) + // Handle error removing patch info, not fatal error + .catch(err => { + this.logProgressLine(`Warning: Could not remove obsolete PatchInfo file at path ${patchInfoPath}: "${err.message}"`) + }) + ops.push(removeOp) + allPaths.push(...patchInfo.appliesTo.map(s => s.path)) + allStatuses.push(...patchInfo.appliesTo.map(({path}) => ({ + path, + patchPath: patchInfoPath.replace(/(.patchinfo)$/, `.${extPatch}`), + reason: patchApplyReasons.PATCH_REMOVED + }))) + } + let resetWasSuccessful = true + // Don't worry about errors with resetting obsolete patch files, + // some paths probably don't exist anymores + const resetOp = this.resetRepoFiles(allPaths) + .catch(() => { + resetWasSuccessful = false + }) + ops.push(resetOp) + await Promise.all(ops) + return allStatuses.map(statusIn => { + const status = { + ...statusIn, + } + if (!resetWasSuccessful) { + status.warning = 'Some resets failed' + } + return status + }) + } +} + +module.exports.patchApplyReasons = patchApplyReasons +module.exports.patchApplyReasonMessages = patchApplyReasonMessages diff --git a/build/lib/gitPatcher.test.js b/build/lib/gitPatcher.test.js new file mode 100644 index 000000000000..4e18a106b29d --- /dev/null +++ b/build/lib/gitPatcher.test.js @@ -0,0 +1,174 @@ +// Copyright (c) 2019 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +const path = require('path') +const fs = require('fs-extra') +const GitPatcher = require('./gitPatcher') +const { runAsync, runGitAsync } = require('./util') +const os = require('os') + +const dirPrefixTmp = 'brave-browser-test-git-apply-' + +const file1InitialContent = 'this is a test' +const file1ModifiedContent = 'this is modified' +const file1Name = 'file1' +const writeReadFileOptions = { encoding: 'utf8' } + +function runGitAsyncWithErrorLog (repoPath, gitArgs) { + return runGitAsync(repoPath, gitArgs, false, true) +} + +function getPatch (gitRepoPath, modifiedFilePath) { + const singleDiffArgs = ['diff', '--src-prefix=a/', '--dst-prefix=b/', '--full-index', modifiedFilePath] + return runGitAsyncWithErrorLog(gitRepoPath, singleDiffArgs) +} + +describe('Apply Patches', function () { + let gitPatcher, repoPath, patchPath, testFile1Path, testFile1PatchPath + + beforeEach(async function () { + // Setup test Git repo and test Patch directory + patchPath = await fs.mkdtemp(path.join(os.tmpdir(), dirPrefixTmp + 'patches-')) + repoPath = await fs.mkdtemp(path.join(os.tmpdir(), dirPrefixTmp)) + testFile1Path = path.join(repoPath, file1Name) + await runGitAsyncWithErrorLog(repoPath, ['init']) + await runGitAsyncWithErrorLog(repoPath, ['config', 'user.email', 'unittests@local']) + await runGitAsyncWithErrorLog(repoPath, ['config', 'user.name', 'Unit Tests']) + await fs.writeFile(testFile1Path, file1InitialContent, writeReadFileOptions) + await runGitAsyncWithErrorLog(repoPath, ['add', '.']) + await runGitAsyncWithErrorLog(repoPath, ['commit', '-m', '"file1 initial"']) + // modify content file + await fs.writeFile(testFile1Path, file1ModifiedContent, writeReadFileOptions) + // get patch + const file1PatchContent = await getPatch(repoPath, file1Name) + // write patch + testFile1PatchPath = path.join(patchPath, file1Name + '.patch') + await fs.writeFile(testFile1PatchPath, file1PatchContent) + // reset file change + await runGitAsyncWithErrorLog(repoPath, ['reset', '--hard', 'HEAD']) + // sanity test + const testFile1Content = await fs.readFile(testFile1Path, writeReadFileOptions) + try { + expect(testFile1Content).toBe(file1InitialContent) + } + catch (err) { + console.error('Setup fail: file was not reset - ' + testFile1Path) + throw new Error(err) + } + gitPatcher = new GitPatcher(patchPath, repoPath, false) + }) + + function validate() { + if (!repoPath || !patchPath) { + throw new Error('test setup failed!') + } + } + + afterEach(async function () { + try { + await fs.remove(repoPath) + } + catch (err) { + console.warn(`Test cleanup: could not remove directory at ${repoPath}`) + } + try { + await fs.remove(patchPath) + } + catch (err) { + console.warn(`Test cleanup: could not remove directory at ${patchPath}`) + } + }) + + test('applies simple patch to unmodified original', async function () { + validate() + const affectedPaths = await gitPatcher.applyPatches() + // test file contents + const testFile1Content = await fs.readFile(testFile1Path, writeReadFileOptions) + expect(testFile1Content).toBe(file1ModifiedContent) + // test reporting + expect(affectedPaths).toHaveLength(1) + expect(affectedPaths[0]).toHaveProperty('path', file1Name) + expect(affectedPaths[0]).toHaveProperty('error', undefined) + expect(affectedPaths[0]).toHaveProperty('reason', GitPatcher.patchApplyReasons.NO_PATCH_INFO) + }) + + test('does not apply patch to still-patched', async function () { + validate() + const testFile1StatsInitial = await fs.stat(testFile1Path) + // apply once + await gitPatcher.applyPatches() + // get modified file time + const testFile1StatsModified = await fs.stat(testFile1Path) + // apply again + const affectedPaths = await gitPatcher.applyPatches() + // Test if the function doesn't think it was modified + expect(affectedPaths).toHaveLength(0) + // Sanity check the file wasn't actually modified + const testFile1StatsAgain = await fs.stat(testFile1Path) + expect(testFile1StatsAgain.mtimeMs).toBe(testFile1StatsModified.mtimeMs) + // Sanity check the file was modified the first time + expect(testFile1StatsModified.mtimeMs).toBeGreaterThan(testFile1StatsInitial.mtimeMs) + }) + + test('resets target file if patch file is removed', async function () { + validate() + // apply once + await gitPatcher.applyPatches() + // remove patch file + await fs.unlink(testFile1PatchPath) + // apply again + const status = await gitPatcher.applyPatches() + expect(status).toHaveLength(1) + expect(status[0]).toHaveProperty('path', file1Name) + expect(status[0]).toHaveProperty('error', undefined) + expect(status[0]).toHaveProperty('reason', GitPatcher.patchApplyReasons.PATCH_REMOVED) + // check file was reset + const testFile1Content = await fs.readFile(testFile1Path, writeReadFileOptions) + expect(testFile1Content).toBe(file1InitialContent) + }) + + test('handles missing file when resets target file if patch file is removed', async function () { + validate() + // apply once + await gitPatcher.applyPatches() + // remove patch file + await fs.unlink(testFile1PatchPath) + // remove target file + await fs.unlink(testFile1Path) + await runGitAsyncWithErrorLog(repoPath, ['commit', '-a', '-m', '"remove target"']) + // apply again + const status = await gitPatcher.applyPatches() + expect(status).toHaveLength(1) + expect(status[0]).toHaveProperty('path', file1Name) + expect(status[0]).toHaveProperty('warning') + expect(status[0]).toHaveProperty('reason', GitPatcher.patchApplyReasons.PATCH_REMOVED) + }) + + test('handles bad patch file', async function () { + validate() + // Create an invalid patch + await fs.writeFile(testFile1PatchPath, 'bad patch', writeReadFileOptions) + const status = await gitPatcher.applyPatches() + expect(status).toHaveLength(1) + expect(status[0]).toHaveProperty('patchPath', testFile1PatchPath) + expect(status[0]).toHaveProperty('path', undefined) + expect(status[0]).toHaveProperty('error') + }) + + test('handles no patch dir', async function () { + validate() + const badPatchPath = path.join(patchPath, 'not-exist') + const noDirPatcher = new GitPatcher(badPatchPath, repoPath) + const status = await noDirPatcher.applyPatches() + expect(status).toHaveLength(0) + }) + + test('handles no repo dir', async function () { + const badRepoPath = path.join(repoPath, 'not-exist') + const noRepoPatcher = new GitPatcher(patchPath, badRepoPath) + await expect(noRepoPatcher.applyPatches()) + .rejects.toThrowError() + }) +}) diff --git a/build/lib/jsconfig.json b/build/lib/jsconfig.json new file mode 100644 index 000000000000..878b851a85a5 --- /dev/null +++ b/build/lib/jsconfig.json @@ -0,0 +1,7 @@ +{ + "typeAcquisition": { + "include": [ + "jest" + ] + } +} \ No newline at end of file diff --git a/build/lib/l10nUtil.js b/build/lib/l10nUtil.js index af6d33559dcf..04df9b4f9c46 100644 --- a/build/lib/l10nUtil.js +++ b/build/lib/l10nUtil.js @@ -38,14 +38,12 @@ const chromiumMediaRouterPartPath = path.resolve(path.join(srcDir, 'chrome', 'ap const braveMediaRouterPartPath = path.resolve(path.join(srcDir, 'brave', 'app', 'media_router_strings.grdp')) const chromiumSettingsStringsPartPath = path.resolve(path.join(srcDir, 'chrome', 'app', 'settings_strings.grdp')) const braveSettingsStringsPartPath = path.resolve(path.join(srcDir, 'brave', 'app', 'settings_strings.grdp')) -const chromiumMdExtensionsPartPath = path.resolve(path.join(srcDir, 'chrome', 'app', 'md_extensions_strings.grdp')) -const braveMdExtensionsPartPath = path.resolve(path.join(srcDir, 'brave', 'app', 'md_extensions_strings.grdp')) +const chromiumExtensionsPartPath = path.resolve(path.join(srcDir, 'chrome', 'app', 'extensions_strings.grdp')) +const braveExtensionsPartPath = path.resolve(path.join(srcDir, 'brave', 'app', 'extensions_strings.grdp')) const chromiumPrintingStringsPartPath = path.resolve(path.join(srcDir, 'chrome', 'app', 'printing_strings.grdp')) const bravePrintingStringsPartPath = path.resolve(path.join(srcDir, 'brave', 'app', 'printing_strings.grdp')) const chromiumProfileSettingsPartPath = path.resolve(path.join(srcDir, 'chrome', 'app', 'profiles_strings.grdp')) const braveProfileSettingsPartPath = path.resolve(path.join(srcDir, 'brave', 'app', 'profiles_strings.grdp')) -const chromiumFileManagerStringsPartPath = path.resolve(path.join(srcDir, 'chrome', 'app', 'file_manager_strings.grdp')) -const braveFileManagerStringsPartPath = path.resolve(path.join(srcDir, 'brave', 'app', 'file_manager_strings.grdp')) const chromiumVRStringsPartPath = path.resolve(path.join(srcDir, 'chrome', 'app', 'vr_strings.grdp')) const braveVRStringsPartPath = path.resolve(path.join(srcDir, 'brave', 'app', 'vr_strings.grdp')) const chromiumOnboardingWelcomeStringsPartPath = path.resolve(path.join(srcDir, 'chrome', 'app', 'onboarding_welcome_strings.grdp')) @@ -76,10 +74,9 @@ const chromiumToAutoGeneratedBraveMapping = { [chromiumBookmarksPartPath]: braveBookmarksPartPath, [chromiumMediaRouterPartPath]: braveMediaRouterPartPath, [chromiumSettingsStringsPartPath]: braveSettingsStringsPartPath, - [chromiumMdExtensionsPartPath]: braveMdExtensionsPartPath, + [chromiumExtensionsPartPath]: braveExtensionsPartPath, [chromiumPrintingStringsPartPath]: bravePrintingStringsPartPath, [chromiumProfileSettingsPartPath]: braveProfileSettingsPartPath, - [chromiumFileManagerStringsPartPath]: braveFileManagerStringsPartPath, [chromiumVRStringsPartPath]: braveVRStringsPartPath, [chromiumOnboardingWelcomeStringsPartPath]: braveOnboardingWelcomeStringsPartPath, [chromiumAppManagementStringsPartPath]: braveAppManagementStringsPartPath diff --git a/build/lib/start.js b/build/lib/start.js index d0efaf0b7ffe..7016d9ce9356 100644 --- a/build/lib/start.js +++ b/build/lib/start.js @@ -6,6 +6,14 @@ const config = require('../lib/config') const util = require('../lib/util') const whitelistedUrlPrefixes = require('./whitelistedUrlPrefixes') const whitelistedUrlPatterns = require('./whitelistedUrlPatterns') +const whitelistedUrlProtocols = [ + 'chrome-extension:', + 'chrome:', + 'brave:', + 'file:', + 'data:', + 'blob:' +] const start = (passthroughArgs, buildConfig = config.defaultBuildConfig, options) => { config.buildConfig = buildConfig @@ -40,6 +48,9 @@ const start = (passthroughArgs, buildConfig = config.defaultBuildConfig, options // This only has meaning with MacOS and official build. braveArgs.push('--disable-brave-update') } + if (options.enable_smart_tracking_protection) { + braveArgs.push('--enable-smart-tracking-protection') + } if (options.single_process) { braveArgs.push('--single-process') } @@ -94,7 +105,8 @@ const start = (passthroughArgs, buildConfig = config.defaultBuildConfig, options stdio: 'inherit', timeout: options.network_log ? 120000 : undefined, continueOnFail: options.network_log ? true : false, - shell: process.platform === 'darwin' ? true : false + shell: process.platform === 'darwin' ? true : false, + killSignal: options.network_log && process.env.RELEASE_TYPE ? 'SIGKILL' : 'SIGTERM' } if (options.network_log) { @@ -127,7 +139,7 @@ const start = (passthroughArgs, buildConfig = config.defaultBuildConfig, options // On windows netlog ends abruptly causing JSON parsing errors if (!jsonContent.endsWith('}]}')) { const n = jsonContent.lastIndexOf('},') - jsonContent = jsonContent.substring(0, n) + "}]}" + jsonContent = jsonContent.substring(0, n) + '}]}' } jsonOutput = JSON.parse(jsonContent) @@ -147,29 +159,35 @@ const start = (passthroughArgs, buildConfig = config.defaultBuildConfig, options if (!url) { return false } - if (url.startsWith('http') && url.includes('.')) { - const foundPrefix = whitelistedUrlPrefixes.find((prefix) => { - return url.startsWith(prefix) - }) - const foundPattern = whitelistedUrlPatterns.find((pattern) => { - return RegExp('^' + pattern).test(url) - }) - if (!foundPrefix && !foundPattern) { - // Check if the URL is a private IP - try { - const hostname = new URL(url).hostname - if (ip.isPrivate(hostname)) { - // Warn but don't fail the audit - console.log('NETWORK AUDIT WARN:', url) - return true - } - } catch (e) {} - // This is not a whitelisted URL! log it and exit with non-zero - console.log('NETWORK AUDIT FAIL:', url) - exitCode = 1 - } - return true + const urlParsed = new URL(url) + const hostname = urlParsed.hostname + if (/^[a-z]+$/.test(hostname)) { + // Chromium sometimes sends requests to random non-resolvable hosts + return false + } + if (whitelistedUrlProtocols.includes(urlParsed.protocol)) { + return false + } + const foundPrefix = whitelistedUrlPrefixes.find((prefix) => { + return url.startsWith(prefix) + }) + const foundPattern = whitelistedUrlPatterns.find((pattern) => { + return RegExp('^' + pattern).test(url) + }) + if (!foundPrefix && !foundPattern) { + // Check if the URL is a private IP + try { + if (ip.isPrivate(hostname)) { + // Warn but don't fail the audit + console.log('NETWORK AUDIT WARN:', url) + return true + } + } catch (e) {} + // This is not a whitelisted URL! log it and exit with non-zero + console.log('NETWORK AUDIT FAIL:', url) + exitCode = 1 } + return true } return false }) diff --git a/build/lib/sync/logging.js b/build/lib/sync/logging.js new file mode 100644 index 000000000000..f885fc891a4d --- /dev/null +++ b/build/lib/sync/logging.js @@ -0,0 +1,87 @@ +// Copyright (c) 2019 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +const os = require('os') +const chalk = require('chalk') +const logUpdate = require('log-update') +const GitPatcher = require('../gitPatcher') + +let divider +function setLineLength () { + divider = Array(process.stdout.columns || 32).join('-') +} +setLineLength() +process.stdout.on('resize', setLineLength) + +const progressStyle = chalk.bold.inverse + +function progressLog (message) { + console.log(progressStyle(message)) +} + +function errorLog (message) { + console.error(progressStyle(message)) +} + +function logUpdateStatus (projectUpdateStatus) { + const statusLines = Object.values(projectUpdateStatus).map(entry => + `${chalk.bold(entry.name)} (${entry.ref}): ${chalk.green.italic(entry.phase)}` + ) + logUpdate(statusLines.join(os.EOL)) +} + +function logAllPatchStatus(allPatchStatus, patchGroupName) { + if (!allPatchStatus.length) { + console.log(chalk.bold.italic(`There were no ${patchGroupName} code patch updates to apply.`)) + } else { + const successfulPatches = [] + const failedPatches = [] + for (const patchStatus of allPatchStatus) { + if (!patchStatus.error) { + successfulPatches.push(patchStatus) + } else { + failedPatches.push(patchStatus) + } + } + console.log(chalk.bold(`There were ${allPatchStatus.length} ${patchGroupName} code patch updates to apply.`)) + if (successfulPatches.length) { + console.log(chalk.green(`${successfulPatches.length} successful patches:`)) + successfulPatches.forEach(logPatchStatus) + } + if (failedPatches.length) { + console.log(chalk.red(`${failedPatches.length} failed patches:`)) + failedPatches.forEach(logPatchStatus) + } + } +} + +function logPatchStatus ({ reason, path, patchPath, error, warning }) { + const success = !error + const statusColor = success ? chalk.green : chalk.red + console.log(statusColor.bold.underline(path || patchPath)) + console.log(` - Patch applied because: ${GitPatcher.patchApplyReasonMessages[reason]}`) + if (error) { + console.log(chalk.red(` - Error - ${error.message}`)) + } + if (warning) { + console.warn(chalk.yellow(` - Warning - ${warning}`)) + } + if (error) { + if (error.stdout) { + console.log(chalk.blue(error.stdout)) + } + if (error.stderr) { + console.error(chalk.red(error.stderr)) + } + } + console.log(divider) +} + +module.exports = { + progressLog, + errorLog, + logUpdateStatus, + logAllPatchStatus +} \ No newline at end of file diff --git a/build/lib/test.js b/build/lib/test.js index db9e18ed2d61..ad4e01ddb08a 100644 --- a/build/lib/test.js +++ b/build/lib/test.js @@ -34,17 +34,44 @@ const test = (suite, buildConfig = config.defaultBuildConfig, options) => { // Build the tests util.run('ninja', ['-C', config.outputDir, suite], config.defaultOptions) - util.run('ninja', ['-C', config.outputDir, "fix_brave_test_install_name"], config.defaultOptions) - let testBinary; - if (process.platform === 'win32') { - testBinary = `${suite}.exe` - } else { - testBinary = suite + const run_brave_installer_unitests = suite === 'brave_unit_tests' + if (run_brave_installer_unitests) { + util.run('ninja', ['-C', config.outputDir, 'brave_installer_unittests'], config.defaultOptions) } - // Run the tests - util.run(path.join(config.outputDir, testBinary), braveArgs, config.defaultOptions) + if (config.targetOS === 'ios') { + util.run(path.join(config.outputDir, "iossim"), [ + path.join(config.outputDir, `${suite}.app`), + path.join(config.outputDir, `${suite}.app/PlugIns/${suite}_module.xctest`) + ], config.defaultOptions) + } else { + util.run('ninja', ['-C', config.outputDir, "fix_brave_test_install_name"], config.defaultOptions) + util.run('ninja', ['-C', config.outputDir, "fix_brave_test_install_name_adblock"], config.defaultOptions) + + let testBinary; + let installerTestBinary; + if (process.platform === 'win32') { + testBinary = `${suite}.exe` + installerTestBinary = 'brave_installer_unittests.exe' + } else { + testBinary = suite + installerTestBinary = 'brave_installer_unittests' + } + + // Run the tests + util.run(path.join(config.outputDir, testBinary), braveArgs, config.defaultOptions) + + if (run_brave_installer_unitests) { + // Replace output file arguments + if (options.output) { + braveArgs.splice(braveArgs.indexOf('--gtest_output=xml:' + options.output, 1)) + braveArgs.push('--gtest_output=xml:brave_installer_unittests.xml') + } + + util.run(path.join(config.outputDir, installerTestBinary), braveArgs, config.defaultOptions) + } + } } module.exports = test diff --git a/build/lib/updatePatches.js b/build/lib/updatePatches.js index c14f7281627c..07ffb85ab7ea 100644 --- a/build/lib/updatePatches.js +++ b/build/lib/updatePatches.js @@ -1,54 +1,20 @@ const path = require('path') const fs = require('fs-extra') -const config = require('../lib/config') const util = require('../lib/util') -const updatePatches = async (options) => { - config.update(options) +const desiredReplacementSeparator = '-' +const patchExtension = '.patch' - const patchDir = path.join(config.projects['brave-core'].dir, 'patches') - - const runOptionsChrome = { cwd: config.projects.chrome.dir } - const runOptionsPatch = { cwd: patchDir } - - const desiredReplacementSeparator = '-' - const patchExtension = '.patch' - - console.log('updatePatches writing files to: ' + patchDir) - - // grab Modified (and later Deleted) files but not Created (since we copy those) +async function getModifiedPaths (gitRepoPath) { const modifiedDiffArgs = ['diff', '--diff-filter=M', '--name-only', '--ignore-space-at-eol'] - const modifiedFileList = (await util.runAsync('git', modifiedDiffArgs, runOptionsChrome)) - .split('\n') - .filter(s => s.length > 0 && - !s.startsWith('chrome/app/theme/default') && - !s.startsWith('chrome/app/theme/brave') && - !s.startsWith('chrome/app/theme/chromium') && - !s.endsWith('.png') && !s.endsWith('.xtb') && - !s.endsWith('.grd') && !s.endsWith('.grdp') && - !s.includes('google_update_idl')) - - // copy array - let substitutedFileList = modifiedFileList.slice() + const cmdOutput = await util.runAsync('git', modifiedDiffArgs, { cwd: gitRepoPath }) + return cmdOutput.split('\n').filter(s => s) +} +async function writePatchFiles (modifiedPaths, gitRepoPath, patchDirPath) { // replacing forward slashes and adding the patch extension to get nice filenames // since git on Windows doesn't use backslashes, this is sufficient - substitutedFileList = substitutedFileList.map(s => s.replace(/\//g, desiredReplacementSeparator) + patchExtension) - - // grab every existing patch file in the dir (at this point, patchfiles for now-unmodified files live on) - const existingFileArgs = ['ls-files', '--exclude-standard'] - let existingFileList = (await util.runAsync('git', existingFileArgs, runOptionsPatch)) - .split('\n').filter(s => s.length > 0) - - // Add files here we specifically want to keep around regardless - const exclusionList = [] - - // Subtract to find which patchfiles no longer have diffs, yet still exist - const minuhend = existingFileList - const subtrahend = substitutedFileList.concat(exclusionList) - const difference = minuhend.filter(x => !subtrahend.includes(x)) - - const cruftList = difference + const patchFilenames = modifiedPaths.map(s => s.replace(/\//g, desiredReplacementSeparator) + patchExtension) // When splitting one large diff into a per-file diff, there are a few ways // you can go about it. Because different files can have the same name @@ -60,31 +26,81 @@ const updatePatches = async (options) => { // appear, you can quickly patch this by changing the separator, even // to something longer + if (modifiedPaths.length) { + await fs.ensureDir(patchDirPath) + } + let writeOpsDoneCount = 0 - let writePatchOps = modifiedFileList.map(async (old, i) => { + let writePatchOps = modifiedPaths.map(async (old, i) => { const singleDiffArgs = ['diff', '--src-prefix=a/', '--dst-prefix=b/', '--full-index', old] - const patchContents = await util.runAsync('git', singleDiffArgs, runOptionsChrome) - const patchFilename = substitutedFileList[i] - await fs.writeFile(path.join(patchDir, patchFilename), patchContents) + const patchContents = await util.runAsync('git', singleDiffArgs, { cwd: gitRepoPath }) + const patchFilename = patchFilenames[i] + await fs.writeFile(path.join(patchDirPath, patchFilename), patchContents) writeOpsDoneCount++ + const logRepoName = path.basename(gitRepoPath) console.log( - `updatePatches wrote ${writeOpsDoneCount} / ${modifiedFileList.length}: ${patchFilename}` + `updatePatches [${logRepoName}] wrote ${writeOpsDoneCount} / ${modifiedPaths.length}: ${patchFilename}` ) }) await Promise.all(writePatchOps) + return patchFilenames +} - // regular rm patchfiles whose target is no longer modified - let m = cruftList.length - for (let i = 0; i < m; i++) { - let filename = cruftList[i] - let fullpath = path.join(patchDir, filename) +const readDirPromise = (pathName) => new Promise((resolve, reject) => + fs.readdir(pathName, (err, fileList) => { + if (err) { + return reject(err) + } + return resolve(fileList) + }) +) - fs.removeSync(fullpath) +async function removeStalePatchFiles (patchFilenames, patchDirPath, keepPatchFilenames) { + // grab every existing patch file in the dir (at this point, patchfiles for now-unmodified files live on) + let existingPathFilenames + try { + existingPathFilenames = ( (await readDirPromise(patchDirPath)) || [] ) + .filter(s => s.endsWith('.patch')) + } catch (err) { + if (err.code === 'ENOENT') { + console.log(`Path at ${patchDirPath} does not exist.`) + return + } + throw err + } + + // Subtract to find which patchfiles no longer have diffs, yet still exist + const validFilenames = patchFilenames.concat(keepPatchFilenames) + const toRemoveFilenames = existingPathFilenames.filter(x => !validFilenames.includes(x)) + + // regular rm patchfiles whose target is no longer modified + let removedProgress = 0 + for (const filename of toRemoveFilenames) { + const fullPath = path.join(patchDirPath, filename) + fs.removeSync(fullPath) + removedProgress++ + console.log(`updatePatches *REMOVED* ${removedProgress}/${toRemoveFilenames.length}: ${filename}`) + } +} - console.log('updatePatches *REMOVED* ' + (1 + i) + '/' + m + ': ' + filename) +/** + * Detects modifications to a git repo and creates or updates patch files for each modified file. + * Removes patch files which are no longer relevant. + * + * @param {*} gitRepoPath Repo path to look for changes + * @param {*} patchDirPath Directory to keep .patch files in + * @param {*} repoPathFilter Filter function for repo file paths to include or exlude (all included by default) + * @param {*} [keepPatchFilenames=[]] Patch filenames to never delete + */ +async function updatePatches (gitRepoPath, patchDirPath, repoPathFilter, keepPatchFilenames = []) { + let modifiedPaths = await getModifiedPaths(gitRepoPath) + if (typeof repoPathFilter === 'function') { + modifiedPaths = modifiedPaths.filter(repoPathFilter) } + const patchFilenames = await writePatchFiles(modifiedPaths, gitRepoPath, patchDirPath) + await removeStalePatchFiles(patchFilenames, patchDirPath, keepPatchFilenames) } module.exports = updatePatches diff --git a/build/lib/util.js b/build/lib/util.js old mode 100644 new mode 100755 index 25b5274bc2a5..cd00699c056e --- a/build/lib/util.js +++ b/build/lib/util.js @@ -19,7 +19,7 @@ const mergeWithDefault = (options) => { const util = { run: (cmd, args = [], options = {}) => { - console.log(cmd, args.join(' ')) + console.log(options.cwd + ':', cmd, args.join(' ')) const continueOnFail = options.continueOnFail delete options.continueOnFail @@ -35,11 +35,12 @@ const util = { }, runAsync: (cmd, args = [], options = {}) => { - console.log(cmd, args.join(' ')) - const continueOnFail = options.continueOnFail - delete options.continueOnFail + let { continueOnFail, verbose, ...cmdOptions } = options + if (verbose) { + console.log(cmd, args.join(' ')) + } return new Promise((resolve, reject) => { - const prog = spawn(cmd, args, options) + const prog = spawn(cmd, args, cmdOptions) let stderr = '' let stdout = '' prog.stderr.on('data', data => { @@ -50,16 +51,43 @@ const util = { }) prog.on('close', statusCode => { const hasFailed = statusCode !== 0 - if (hasFailed && !continueOnFail) { + if (verbose && (!hasFailed || continueOnFail)) { console.log(stdout) - console.error(stderr) - process.exit(1) + if (stderr) { + console.error(stderr) + } + } + if (hasFailed) { + const err = new Error(`Program ${cmd} exited with error code ${statusCode}.`) + err.stderr = stderr + err.stdout = stdout + reject(err) + if (!continueOnFail) { + console.log(err.message) + console.log(stdout) + console.error(stderr) + process.exit(1) + } + return } resolve(stdout) }) }) }, + runGitAsync: function (repoPath, gitArgs, verbose = false, logError = false) { + return util.runAsync('git', gitArgs, { cwd: repoPath, verbose, continueOnFail: true }) + .catch(err => { + if (logError) { + console.error(err.message) + console.error(`Git arguments were: ${gitArgs.join(' ')}`) + console.log(err.stdout) + console.error(err.stderr) + } + return Promise.reject(err) + }) + }, + buildGClientConfig: () => { function replacer(key, value) { return value; @@ -75,9 +103,17 @@ const util = { } }) - const out = 'solutions = ' + JSON.stringify(solutions, replacer, 2) - .replace(/"%None%"/g, "None") - .replace(/"%False%"/g, "False") + let cache_dir = process.env.GIT_CACHE_PATH ? ('\ncache_dir = "' + process.env.GIT_CACHE_PATH + '"\n') : '\n' + + let out = 'solutions = ' + JSON.stringify(solutions, replacer, 2) + .replace(/"%None%"/g, "None").replace(/"%False%"/g, "False") + cache_dir + + if (config.targetOS === 'android') { + out = out + "target_os = [ 'android' ]" + } else if (config.targetOS === 'ios') { + out = out + "target_os = [ 'ios' ]" + } + fs.writeFileSync(config.defaultGClientFile, out) }, @@ -194,6 +230,42 @@ const util = { fs.copySync(brandingSource, brandingDest) } } + if (config.targetOS === 'android') { + const androidIconSet = (config.channel === 'development' ? 'res_brave_default' : 'res_brave') + const androidIconSource = path.join(braveAppDir, 'theme', 'brave', 'android', androidIconSet) + const androidIconDest = path.join(config.srcDir, 'chrome', 'android', 'java', 'res_chromium') + const androidResSource = path.join(config.projects['brave-core'].dir, 'android', 'java', 'res') + const androidResDest = path.join(config.srcDir, 'chrome', 'android', 'java', 'res') + + let androidSourceFiles = [] + if (fs.statSync(androidIconSource).isDirectory()) { + androidSourceFiles = util.walkSync(androidIconSource) + } else { + androidSourceFiles = [androidIconSource] + } + + console.log('copy Android app icons') + for (const androidSourceFile of androidSourceFiles) { + let destinationFile = path.join(androidIconDest, path.relative(androidIconSource, androidSourceFile)) + if (util.calculateFileChecksum(androidSourceFile) != util.calculateFileChecksum(destinationFile)) { + fs.copySync(androidSourceFile, destinationFile) + } + } + + console.log('copy Android app resources') + androidSourceFiles = [] + if (fs.statSync(androidResSource).isDirectory()) { + androidSourceFiles = util.walkSync(androidResSource) + } else { + androidSourceFiles = [androidResSource] + } + for (const androidSourceFile of androidSourceFiles) { + let destinationFile = path.join(androidResDest, path.relative(androidResSource, androidSourceFile)) + if (!fs.existsSync(destinationFile) || (util.calculateFileChecksum(androidSourceFile) != util.calculateFileChecksum(destinationFile))) { + fs.copySync(androidSourceFile, destinationFile) + } + } + } }, // Chromium compares pre-installed midl files and generated midl files from IDL during the build to check integrity. @@ -233,8 +305,7 @@ const util = { #endif // WIDEVINE_CDM_VERSION_H_` // If version file or fake lib file aren't existed, create them. - if (!fs.existsSync(widevineConfig.widevineCdmHeaderFilePath) || - !fs.existsSync(widevineConfig.widevineCdmHeaderFilePath)) { + if (!fs.existsSync(widevineConfig.widevineCdmHeaderFilePath) || !fs.existsSync(widevineConfig.fakeWidevineCdmLibFilePath)) { util.doPrepareWidevineCdmBuild(widevineConfig) return } @@ -264,6 +335,65 @@ const util = { util.run('ninja', ['-C', config.outputDir, config.signTarget], options) }, + signWinBinaries: () => { + if (config.buildConfig !== 'Release' || config.skip_signing) { + console.log('binary signing is skipped because this is not official build or --skip_signing is used') + return + } + + if (process.env.CERT === undefined || process.env.SIGNTOOL_ARGS === undefined) { + console.log('binary signing is skipped because of missing env vars') + return + } + + console.log('signing win binaries...') + + // Copy & sign only binaries for widevine sig file generation. + // With this, create_dist doesn't trigger rebuild because original binaries is not modified. + const dir = path.join(config.outputDir, 'signed_binaries') + if (!fs.existsSync(dir)) + fs.mkdirSync(dir); + + fs.copySync(path.join(config.outputDir, 'brave.exe'), path.join(dir, 'brave.exe')); + fs.copySync(path.join(config.outputDir, 'chrome.dll'), path.join(dir, 'chrome.dll')); + fs.copySync(path.join(config.outputDir, 'chrome_child.dll'), path.join(dir, 'chrome_child.dll')); + + const core_dir = config.projects['brave-core'].dir + util.run('python', [path.join(core_dir, 'script', 'sign_binaries.py'), '--build_dir=' + dir]) + }, + + generateWidevineSigFiles: () => { + const cert = config.sign_widevine_cert + const key = config.sign_widevine_key + const passwd = config.sign_widevine_passwd + const sig_generator = config.signature_generator + let src_dir = path.join(config.outputDir, 'signed_binaries') + + if (config.skip_signing || process.env.CERT === undefined || process.env.SIGNTOOL_ARGS === undefined) + src_dir = config.outputDir + + console.log('generate Widevine sig files...') + + util.run('python', [sig_generator, '--input_file=' + path.join(src_dir, 'brave.exe'), + '--flags=1', + '--certificate=' + cert, + '--private_key=' + key, + '--output_file=' + path.join(config.outputDir, 'brave.exe.sig'), + '--private_key_passphrase=' + passwd]) + util.run('python', [sig_generator, '--input_file=' + path.join(src_dir, 'chrome.dll'), + '--flags=0', + '--certificate=' + cert, + '--private_key=' + key, + '--output_file=' + path.join(config.outputDir, 'chrome.dll.sig'), + '--private_key_passphrase=' + passwd]) + util.run('python', [sig_generator, '--input_file=' + path.join(src_dir, 'chrome_child.dll'), + '--flags=0', + '--certificate=' + cert, + '--private_key=' + key, + '--output_file=' + path.join(config.outputDir, 'chrome_child.dll.sig'), + '--private_key_passphrase=' + passwd]) + }, + buildTarget: (options = config.defaultOptions) => { console.log('building ' + config.buildTarget + '...') @@ -276,7 +406,30 @@ const util = { const args = util.buildArgsToString(config.buildArgs()) util.run('gn', ['gen', config.outputDir, '--args="' + args + '"'], options) - util.run('ninja', ['-C', config.outputDir, config.buildTarget, '-k', num_compile_failure], options) + + let ninjaOpts = [ + '-C', config.outputDir, config.buildTarget, + '-k', num_compile_failure, + ...config.extraNinjaOpts + ] + util.run('ninja', ninjaOpts, options) + }, + + generateXcodeWorkspace: (options = config.defaultOptions) => { + console.log('generating Xcode workspace for "' + config.xcode_gen_target + '"...') + + const args = util.buildArgsToString(config.buildArgs()) + const genScript = path.join(config.rootDir, 'vendor', 'gn-project-generators', 'xcode.py') + + const genArgs = [ + 'gen', config.outputDir + "_Xcode", + '--args="' + args + '"', + '--ide=json', + '--json-ide-script="' + genScript + '"', + '--filters="' + config.xcode_gen_target + '"' + ] + + util.run('gn', genArgs, options) }, lint: (options = {}) => { @@ -310,23 +463,23 @@ const util = { runGClient(['runhooks'], options) }, - fetch: (options = {}) => { - if (!options.cwd) options.cwd = config.rootDir - options = mergeWithDefault(options) - util.run('git', ['-C', options.git_cwd, 'fetch', '--all', '--tags'], options) - }, - - setVersion: (version, options = {}) => { - if (!options.cwd) options.cwd = config.rootDir - util.run('git', ['-C', options.git_cwd, 'clean', '-f'], options) - util.run('git', ['-C', options.git_cwd, 'reset', '--hard', version], options) + fetch: (gitRepoPath) => { + return util.runGitAsync(gitRepoPath, ['fetch', '--all', '--tags']) }, - setDepVersion: (dir, version) => { - const options = { git_cwd: dir } - util.fetch(options) - util.setVersion(version, options) - }, + setGitVersion: async (gitRepoPath, version, alwaysReset = false) => { + await util.runGitAsync(gitRepoPath, ['clean', '-f']) + let shouldReset = alwaysReset + if (!shouldReset) { + const headSHA = await util.runGitAsync(gitRepoPath, ['rev-parse', 'HEAD']) + const targetSHA = await util.runGitAsync(gitRepoPath, ['rev-parse', version]) + shouldReset = (headSHA !== targetSHA) + } + if (shouldReset) { + await util.runGitAsync(gitRepoPath, ['reset', '--hard', version]) + } + return shouldReset + }, buildArgsToString: (buildArgs) => { let args = '' diff --git a/build/lib/whitelistedUrlPatterns.js b/build/lib/whitelistedUrlPatterns.js index 64e081ae3b29..b3e445511521 100644 --- a/build/lib/whitelistedUrlPatterns.js +++ b/build/lib/whitelistedUrlPatterns.js @@ -1,7 +1,7 @@ // Before adding to this list, get approval from the security team module.exports = [ - 'http://[A-Za-z0-9-\.]+\.gvt1\.com/edgedl/release2/chrome_component/.+crl-set.+', // allowed because it 307's to crlsets.brave.com - 'https://[A-Za-z0-9-\.]+\.gvt1\.com/edgedl/release2/chrome_component/.+crl-set.+', // allowed because it 307's to crlsets.brave.com + 'http://[A-Za-z0-9-\.]+\.gvt1\.com/edgedl/release2/chrome_component/.+', // allowed because it 307's to crlsets.brave.com + 'https://[A-Za-z0-9-\.]+\.gvt1\.com/edgedl/release2/chrome_component/.+', // allowed because it 307's to crlsets.brave.com 'http://www.google.com/dl/release2/chrome_component/.+crl-set.+', // allowed because it 307's to crlsets.brave.com 'https://www.google.com/dl/release2/chrome_component/.+crl-set.+', // allowed because it 307's to crlsets.brave.com 'http://storage.googleapis.com/update-delta/hfnkpimlhhgieaddgfemjhofmfblmnib/.+crxd', // allowed because it 307's to crlsets.brave.com, diff --git a/build/scripts/audit.js b/build/scripts/audit.js index 2221871805c3..f59c6979a66d 100644 --- a/build/scripts/audit.js +++ b/build/scripts/audit.js @@ -16,7 +16,8 @@ const syncDir = path.join(braveDir, 'components', 'brave_sync', 'extension') */ function npmAudit (pathname) { if (fs.existsSync(path.join(pathname, 'package.json')) && - fs.existsSync(path.join(pathname, 'package-lock.json'))) { + fs.existsSync(path.join(pathname, 'package-lock.json')) && + fs.existsSync(path.join(pathname, 'node_modules'))) { console.log('Auditing', pathname) let cmdOptions = { cwd: pathname, @@ -24,7 +25,7 @@ function npmAudit (pathname) { } util.run('npm', ['audit'], cmdOptions) } else { - console.log('Skipping audit of', pathname) + console.log('Skipping audit of "' + pathname + '" (no package.json or node_modules directory found)') } } @@ -35,4 +36,4 @@ fs.readdirSync(braveVendorDir).forEach((dir) => { }) fs.readdirSync(syncDir).forEach((dir) => { npmAudit(path.join(syncDir, dir)) -}) +}) \ No newline at end of file diff --git a/build/scripts/commands.js b/build/scripts/commands.js old mode 100644 new mode 100755 index dbf1ea0eaaae..d4019d349e86 --- a/build/scripts/commands.js +++ b/build/scripts/commands.js @@ -10,7 +10,7 @@ const util = require('../lib/util') const build = require('../lib/build') const versions = require('../lib/versions') const start = require('../lib/start') -const updatePatches = require('../lib/updatePatches') +const updatePatches = require('./updatePatches') const pullL10n = require('../lib/pullL10n') const pushL10n = require('../lib/pushL10n') const chromiumRebaseL10n = require('../lib/chromiumRebaseL10n') @@ -18,6 +18,11 @@ const createDist = require('../lib/createDist') const upload = require('../lib/upload') const test = require('../lib/test') +const collect = (value, accumulator) => { + accumulator.push(value) + return accumulator +} + const parsedArgs = program.parseOptions(process.argv) program @@ -30,15 +35,22 @@ program program .command('build') .option('-C ', 'build config (out/Debug, out/Release') + .option('--target_os ', 'target OS') .option('--target_arch ', 'target architecture', 'x64') + .option('--target_apk_base ', 'target Android OS apk (classic, modern, mono)', 'classic') .option('--mac_signing_identifier ', 'The identifier to use for signing') .option('--mac_signing_keychain ', 'The identifier to use for signing', 'login') .option('--debug_build ', 'keep debugging symbols') .option('--official_build ', 'force official build settings') .option('--brave_google_api_key ') .option('--brave_google_api_endpoint ') + .option('--brave_infura_project_id ') .option('--channel ', 'target channel to build', /^(beta|dev|nightly|release)$/i, 'release') .option('--ignore_compile_failure', 'Keep compiling regardless of error') + .option('--skip_signing', 'skip signing binaries') + .option('--xcode_gen ', 'Generate an Xcode workspace ("ios" or a list of semi-colon separated label patterns, run `gn help label_pattern` for more info.') + .option('--gn ', 'Additional gn args, in the form :', collect, []) + .option('--ninja ', 'Additional Ninja command-line options, in the form :', collect, []) .arguments('[build_config]') .action(build) @@ -53,6 +65,7 @@ program .option('--official_build ', 'force official build settings') .option('--brave_google_api_key ') .option('--brave_google_api_endpoint ') + .option('--brave_infura_project_id ') .option('--channel ', 'target channel to build', /^(beta|dev|nightly|release)$/i, 'release') .option('--build_omaha', 'build omaha stub/standalone installer') .option('--tag_ap ', 'ap for stub/standalone installer') @@ -125,6 +138,8 @@ program .option('--disable_brave_extension', 'disable loading the Brave extension') .option('--single_process', 'uses a single process to run tests to help with debugging') .option('--test_launcher_jobs ', 'Number of jobs to launch') + .option('--target_os ', 'target OS') + .option('--target_arch ', 'target architecture', 'x64') .arguments('[build_config]') .action(test) diff --git a/build/scripts/sync.js b/build/scripts/sync.js index a2ce5767e436..3e36d298d89b 100644 --- a/build/scripts/sync.js +++ b/build/scripts/sync.js @@ -1,8 +1,17 @@ +// Copyright (c) 2019 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + const path = require('path') -const program = require('commander'); +const os = require('os') +const program = require('commander') const fs = require('fs-extra') +const chalk = require('chalk') const config = require('../lib/config') const util = require('../lib/util') +const GitPatcher = require('../lib/gitPatcher') +const { progressLog, errorLog, logUpdateStatus, logAllPatchStatus } = require('../lib/sync/logging') const projectNames = config.projectNames.filter((project) => config.projects[project].ref) @@ -12,6 +21,9 @@ program .option('--gclient_verbose', 'verbose output for gclient') .option('--run_hooks', 'run gclient hooks') .option('--run_sync', 'run gclient sync') + .option('--target_os ', 'target OS') + .option('--target_arch ', 'target architecture') + .option('--target_apk_base ', 'target Android OS apk (classic, modern, mono)') .option('--submodule_sync', 'run submodule sync') .option('--init', 'initialize all dependencies') .option('--all', 'update all projects') @@ -20,37 +32,104 @@ projectNames.forEach((name) => { program.option('--' + project.arg_name + '_ref ', name + ' ref to checkout') }) -program.parse(process.argv) -config.update(program) - -if (program.init || program.submodule_sync) { - util.submoduleSync() -} - -if (program.init) { - util.buildGClientConfig() -} - -if (program.init) { - util.gclientSync(true) -} +async function RunCommand () { + program.parse(process.argv) + config.update(program) + + if (program.init || program.submodule_sync) { + progressLog('Updating submodules...') + util.submoduleSync() + progressLog('Done updating submodules...') + } + + if (program.init) { + util.buildGClientConfig() + } + + if (program.init) { + progressLog(`Syncing Gclient (with reset)`) + util.gclientSync(true) + } + + progressLog('Updating project repositories...') + const alwaysReset = program.init ? true : false + if (alwaysReset) { + console.log(chalk.italic('A git reset will be performed for all repositories because the "init" param was specified')) + } + let wasSomeDepUpdated = alwaysReset ? true : false + + const projectUpdateStatus = {} + await Promise.all( + projectNames.map(async (name) => { + let project = config.projects[name] + if (alwaysReset || program.all || program[project.arg_name + '_ref']) { + projectUpdateStatus[name] = { + name, + phase: 'fetching...', + ref: project.ref + } + logUpdateStatus(projectUpdateStatus) + await util.fetch(project.dir) + projectUpdateStatus[name].phase = 'ensuring up to date...' + logUpdateStatus(projectUpdateStatus) + const thisDepUpdated = await util.setGitVersion(project.dir, project.ref, alwaysReset) + projectUpdateStatus[name].phase = `done (reset ${thisDepUpdated ? 'was' : 'not'} required).` + projectUpdateStatus[name].requiredUpdate = thisDepUpdated + logUpdateStatus(projectUpdateStatus) + if (thisDepUpdated) { + wasSomeDepUpdated = true + } + } + }) + ) + progressLog('Done updating project repositories.') + + if (wasSomeDepUpdated || alwaysReset || program.run_sync) { + progressLog(`Running gclient sync (${alwaysReset ? '' : 'not '}with reset)...`) + util.gclientSync(alwaysReset) + progressLog('Done running gclient sync.') + } + + progressLog('Applying patches...') + // Always detect if we need to apply patches, since user may have modified + // either chromium source files, or .patch files manually + const coreRepoPath = config.projects['brave-core'].dir + const patchesPath = path.join(coreRepoPath, 'patches') + const v8PatchesPath = path.join(patchesPath, 'v8') + const chromiumRepoPath = config.projects['chrome'].dir + const v8RepoPath = path.join(chromiumRepoPath, 'v8') + const chromiumPatcher = new GitPatcher(patchesPath, chromiumRepoPath) + const v8Patcher = new GitPatcher(v8PatchesPath, v8RepoPath) -let updatedVersion = false + const chromiumPatchStatus = await chromiumPatcher.applyPatches() + const v8PatchStatus = await v8Patcher.applyPatches() -projectNames.forEach((name) => { - let project = config.projects[name] - if (program.init || program.all || program[project.arg_name + '_ref']) { - updatedVersion = true - util.setDepVersion(project.dir, project.ref) + // Log status for all patches + // Differentiate entries for logging + v8PatchStatus.forEach(s => s.path = path.join('v8', s.path)) + const allPatchStatus = chromiumPatchStatus.concat(v8PatchStatus) + logAllPatchStatus(allPatchStatus, 'Chromium') + const hasPatchError = allPatchStatus.some(p => p.error) + // Exit on error in any patch + if (hasPatchError) { + console.error(chalk.red.bgBlack('Exiting as not all patches were successful!')) + process.exit(1) } -}) + progressLog('Done applying patches.') -if (updatedVersion || program.init || program.run_sync) { - util.gclientSync() + if (wasSomeDepUpdated || program.init || program.run_hooks) { + progressLog('Running gclient hooks...') + util.gclientRunhooks() + progressLog('Done running gclient hooks.') + } } -if (updatedVersion || program.init || program.run_hooks) { - const core_dir = config.projects['brave-core'].dir - util.run('python', [path.join(core_dir, 'script', 'apply-patches.py')]) - util.gclientRunhooks() -} +progressLog('Brave Browser Sync starting') +RunCommand() +.then(() => { + progressLog('Brave Browser Sync complete') +}) +.catch((err) => { + errorLog('Brave Browser Sync ERROR:') + console.error(err) +}) diff --git a/build/scripts/updatePatches.js b/build/scripts/updatePatches.js new file mode 100644 index 000000000000..d34fa37a2007 --- /dev/null +++ b/build/scripts/updatePatches.js @@ -0,0 +1,34 @@ +const path = require('path') +const config = require('../lib/config') +const updatePatches = require('../lib/updatePatches') + +const chromiumPathFilter = (s) => s.length > 0 && + !s.startsWith('chrome/app/theme/default') && + !s.startsWith('chrome/app/theme/brave') && + !s.startsWith('chrome/app/theme/chromium') && + !s.endsWith('.png') && !s.endsWith('.xtb') && + !s.endsWith('.grd') && !s.endsWith('.grdp') && + !s.includes('google_update_idl') + +module.exports = function RunCommand (options) { + config.update(options) + + const chromiumDir = config.projects.chrome.dir + const v8Dir = path.join(config.projects.chrome.dir, 'v8') + const patchDir = path.join(config.projects['brave-core'].dir, 'patches') + const v8PatchDir = path.join(patchDir, 'v8') + + Promise.all([ + // chromium + updatePatches(chromiumDir, patchDir, chromiumPathFilter), + // v8 + updatePatches(v8Dir, v8PatchDir) + ]) + .then(() => { + console.log('Done.') + }) + .catch(err => { + console.error('Error updating patch files:') + console.error(err) + }) +}