diff --git a/__snapshots__/upload-spec.js b/__snapshots__/upload-spec.js index 5a988491d37c..20221f9af2b7 100644 --- a/__snapshots__/upload-spec.js +++ b/__snapshots__/upload-spec.js @@ -23,6 +23,9 @@ exports['test runner manifest'] = { "linux-x64": { "url": "https://cdn.cypress.io/desktop/3.3.0/linux-x64/cypress.zip" }, + "linux-arm64": { + "url": "https://cdn.cypress.io/desktop/3.3.0/linux-arm64/cypress.zip" + }, "win32-x64": { "url": "https://cdn.cypress.io/desktop/3.3.0/win32-x64/cypress.zip" } diff --git a/__snapshots__/util-upload-spec.js b/__snapshots__/util-upload-spec.js index dbe833827a3e..f00c451e4c15 100644 --- a/__snapshots__/util-upload-spec.js +++ b/__snapshots__/util-upload-spec.js @@ -13,6 +13,10 @@ exports['upload util isValidPlatformArch checks given strings second 1'] = { "given": "linux-x64", "expect": true }, + { + "given": "linux-arm64", + "expect": true + }, { "given": "win32-x64", "expect": true diff --git a/circle.yml b/circle.yml index 0aa5f48748c1..0241aa54efaf 100644 --- a/circle.yml +++ b/circle.yml @@ -22,14 +22,13 @@ defaults: &defaults COLUMNS: 100 LINES: 24 -# filters and requires for testing binary with Firefox mainBuildFilters: &mainBuildFilters filters: branches: only: - develop - 10.0-release - - use-m1-runners + - linux-arm64 # usually we don't build Mac app - it takes a long time # but sometimes we want to really confirm we are doing the right thing @@ -38,14 +37,23 @@ macWorkflowFilters: &darwin-workflow-filters when: or: - equal: [ develop, << pipeline.git.branch >> ] - - equal: [ use-m1-runners, << pipeline.git.branch >> ] + - equal: [ linux-arm64, << pipeline.git.branch >> ] + - matches: + pattern: "-release$" + value: << pipeline.git.branch >> + +linuxArm64WorkflowFilters: &linux-arm64-workflow-filters + when: + or: + - equal: [ develop, << pipeline.git.branch >> ] + - equal: [ linux-arm64, << pipeline.git.branch >> ] - matches: pattern: "-release$" value: << pipeline.git.branch >> # uncomment & add to the branch conditions below to disable the main linux # flow if we don't want to test it for a certain branch -linuxWorkflowExcludeFilters: &linux-workflow-exclude-filters +linuxWorkflowExcludeFilters: &linux-x64-workflow-exclude-filters unless: or: - false @@ -58,7 +66,7 @@ fullWindowsWorkflowFilters: &full-windows-workflow-filters branches: only: - develop - - 'use-m1-runners' + - 'linux-arm64' - '*-release' - 'win*' @@ -108,13 +116,20 @@ executors: environment: PLATFORM: darwin + linux-arm64: + machine: + image: ubuntu-2004:2022.04.1 + resource_class: arm.medium + environment: + PLATFORM: linux + commands: verify_should_persist_artifacts: steps: - run: name: Check current branch to persist artifacts command: | - if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "use-m1-runners" ]]; then + if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "linux-arm64" ]]; then echo "Not uploading artifacts or posting install comment for this branch." circleci-agent step halt fi @@ -955,12 +970,24 @@ commands: # notarization on Mac can take a while no_output_timeout: "45m" command: | + if [[ `node ./scripts/get-platform-key.js` == 'linux-arm64' ]]; then + # these are missing on Circle and there is no way to pre-install them on Arm + sudo apt-get update + sudo apt-get install -y libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb + fi node --version yarn binary-build --version $(node ./scripts/get-next-version.js) - run: name: Zip the binary command: | - [[ $PLATFORM == 'linux' ]] && apt-get update && apt-get install -y zip || [[ $PLATFORM != 'linux' ]] + if [[ $PLATFORM == 'linux' ]]; then + # on Arm, CI runs as non-root, on x64 CI runs as root but there is no sudo binary + if [[ `whoami` == 'root' ]]; then + apt-get update && apt-get install -y zip + else + sudo apt-get update && sudo apt-get install -y zip + fi + fi yarn binary-zip - store-npm-logs - persist_to_workspace: @@ -2236,7 +2263,7 @@ jobs: command: DEBUG=cypress:cli $(yarn bin cypress) run - store-npm-logs -linux-workflow: &linux-workflow +linux-x64-workflow: &linux-x64-workflow jobs: - node_modules_install - build: @@ -2545,6 +2572,31 @@ linux-workflow: &linux-workflow - create-build-artifacts - system-tests-node-modules-install +linux-arm64-workflow: &linux-arm64-workflow + jobs: + - node_modules_install: + name: linux-arm64-node-modules-install + executor: linux-arm64 + resource_class: arm.medium + only-cache-for-root-user: true + + - build: + name: linux-arm64-build + executor: linux-arm64 + resource_class: arm.medium + requires: + - linux-arm64-node-modules-install + + - create-build-artifacts: + name: linux-arm64-create-build-artifacts + context: + - test-runner:upload + - test-runner:commit-status-checks + executor: linux-arm64 + resource_class: arm.medium + requires: + - linux-arm64-build + darwin-x64-workflow: &darwin-x64-workflow jobs: - node_modules_install: @@ -2675,9 +2727,12 @@ windows-workflow: &windows-workflow - windows-create-build-artifacts workflows: - linux: - <<: *linux-workflow - <<: *linux-workflow-exclude-filters + linux-x64: + <<: *linux-x64-workflow + <<: *linux-x64-workflow-exclude-filters + linux-arm64: + <<: *linux-arm64-workflow + <<: *linux-arm64-workflow-filters darwin-x64: <<: *darwin-x64-workflow <<: *darwin-workflow-filters diff --git a/cli/.eslintrc.json b/cli/.eslintrc.json new file mode 100644 index 000000000000..0ec2a731da39 --- /dev/null +++ b/cli/.eslintrc.json @@ -0,0 +1,20 @@ +{ + "extends": "../.eslintrc.js", + "rules": { + "no-restricted-syntax": [ + "error", + { + "selector": "CallExpression[callee.name='arch']", + "message": "Do not use `arch()` to detect the user's machine architecture. Use util.getRealArch() instead." + }, + { + "selector": "CallExpression[callee.object.name='os'][callee.property.name='arch']", + "message": "Do not use `os.arch()` to detect the user's machine architecture. Use util.getRealArch() instead." + }, + { + "selector": "MemberExpression[object.name='process'][property.name='arch']", + "message": "Do not use `process.arch` to detect the user's machine architecture. Use util.getRealArch() instead." + } + ] + } +} diff --git a/cli/lib/tasks/install.js b/cli/lib/tasks/install.js index 0ee2ab4225b8..ea88f34f9336 100644 --- a/cli/lib/tasks/install.js +++ b/cli/lib/tasks/install.js @@ -134,7 +134,7 @@ const downloadAndUnzip = ({ version, installDir, downloadDir }) => { const validateOS = () => { return util.getPlatformInfo().then((platformInfo) => { - return platformInfo.match(/(win32-x64|linux-x64|darwin-x64|darwin-arm64)/) + return platformInfo.match(/(win32-x64|linux-x64|linux-arm64|darwin-x64|darwin-arm64)/) }) } diff --git a/cli/lib/util.js b/cli/lib/util.js index 6764cad62971..5604e313c23a 100644 --- a/cli/lib/util.js +++ b/cli/lib/util.js @@ -481,22 +481,32 @@ const util = { async function _getRealArch () { const osPlatform = os.platform() + // eslint-disable-next-line no-restricted-syntax const osArch = os.arch() debug('detecting arch %o', { osPlatform, osArch }) - if (osPlatform === 'darwin') { - if (osArch === 'arm64') return 'arm64' + if (osArch === 'arm64') return 'arm64' + if (osPlatform === 'darwin') { // could possibly be x64 node on arm64 darwin, check if we are being translated by Rosetta // https://stackoverflow.com/a/65347893/3474615 const { stdout } = await execa('sysctl', ['-n', 'sysctl.proc_translated']).catch(() => '') debug('rosetta check result: %o', { stdout }) - if (stdout === '1') return 'arm64' } + if (osPlatform === 'linux') { + // could possibly be x64 node on arm64 linux, check the "machine hardware name" + // list of names for reference: https://stackoverflow.com/a/45125525/3474615 + const { stdout } = await execa('uname', ['-m']).catch(() => '') + + debug('arm uname -m result: %o ', { stdout }) + if (['aarch64_be', 'aarch64', 'armv8b', 'armv8l'].includes(stdout)) return 'arm64' + } + + // eslint-disable-next-line no-restricted-syntax const pkgArch = arch() if (pkgArch === 'x86') return 'ia32' diff --git a/cli/test/lib/tasks/download_spec.js b/cli/test/lib/tasks/download_spec.js index 3562ece44cef..ee88fc234de5 100644 --- a/cli/test/lib/tasks/download_spec.js +++ b/cli/test/lib/tasks/download_spec.js @@ -478,44 +478,91 @@ describe('lib/tasks/download', function () { sinon.stub(os, 'arch') }) - function nockDarwinArm64 () { - return nock('https://download.cypress.io') - .get('/desktop/1.2.3') - .query({ arch: 'arm64', platform: 'darwin' }) - .reply(200, undefined, { - 'x-version': '1.2.3', + context('Apple Silicon/M1', () => { + function nockDarwinArm64 () { + return nock('https://download.cypress.io') + .get('/desktop/1.2.3') + .query({ arch: 'arm64', platform: 'darwin' }) + .reply(200, undefined, { + 'x-version': '1.2.3', + }) + } + + it('downloads darwin-arm64 on M1', async function () { + os.platform.returns('darwin') + os.arch.returns('arm64') + nockDarwinArm64() + + const responseVersion = await download.start(this.options) + + expect(responseVersion).to.eq('1.2.3') + + await fs.statAsync(downloadDestination) }) - } - it('downloads darwin-arm64 on M1', async function () { - os.platform.returns('darwin') - os.arch.returns('arm64') - nockDarwinArm64() + it('downloads darwin-arm64 on M1 translated by Rosetta', async function () { + os.platform.returns('darwin') + os.arch.returns('x64') + nockDarwinArm64() + + sinon.stub(cp, 'spawn').withArgs('sysctl', ['-n', 'sysctl.proc_translated']) + .callsFake(mockSpawn(((cp) => { + cp.stdout.write('1') + cp.emit('exit', 0, null) + cp.end() + }))) - const responseVersion = await download.start(this.options) + const responseVersion = await download.start(this.options) - expect(responseVersion).to.eq('1.2.3') + expect(responseVersion).to.eq('1.2.3') - await fs.statAsync(downloadDestination) + await fs.statAsync(downloadDestination) + }) }) - it('downloads darwin-arm64 on M1 translated by Rosetta', async function () { - os.platform.returns('darwin') - os.arch.returns('x64') - nockDarwinArm64() + context('Linux arm64/aarch64', () => { + function nockLinuxArm64 () { + return nock('https://download.cypress.io') + .get('/desktop/1.2.3') + .query({ arch: 'arm64', platform: 'linux' }) + .reply(200, undefined, { + 'x-version': '1.2.3', + }) + } + + it('downloads linux-arm64 on arm64 processor', async function () { + os.platform.returns('linux') + os.arch.returns('arm64') + nockLinuxArm64() - sinon.stub(cp, 'spawn').withArgs('sysctl', ['-n', 'sysctl.proc_translated']) - .callsFake(mockSpawn(((cp) => { - cp.stdout.write('1') - cp.emit('exit', 0, null) - cp.end() - }))) + const responseVersion = await download.start(this.options) - const responseVersion = await download.start(this.options) + expect(responseVersion).to.eq('1.2.3') - expect(responseVersion).to.eq('1.2.3') + await fs.statAsync(downloadDestination) + }) - await fs.statAsync(downloadDestination) + it('downloads linux-arm64 on non-arm64 node running on arm machine', async function () { + os.platform.returns('linux') + os.arch.returns('x64') + sinon.stub(cp, 'spawn') + + for (const arch of ['aarch64_be', 'aarch64', 'armv8b', 'armv8l']) { + nockLinuxArm64() + cp.spawn.withArgs('uname', ['-m']) + .callsFake(mockSpawn(((cp) => { + cp.stdout.write(arch) + cp.emit('exit', 0, null) + cp.end() + }))) + + const responseVersion = await download.start(this.options) + + expect(responseVersion).to.eq('1.2.3') + + await fs.statAsync(downloadDestination) + } + }) }) }) diff --git a/package.json b/package.json index 9375f8fdb375..fe91cf53b141 100644 --- a/package.json +++ b/package.json @@ -141,7 +141,7 @@ "dedent": "^0.7.0", "del": "3.0.0", "detect-port": "^1.3.0", - "electron": "18.0.4", + "electron": "18.3.0", "electron-builder": "^22.13.1", "electron-notarize": "^1.1.1", "enzyme-adapter-react-16": "1.12.1", diff --git a/scripts/binary/index.js b/scripts/binary/index.js index 26270b9ef6ee..00efe9790f68 100644 --- a/scripts/binary/index.js +++ b/scripts/binary/index.js @@ -113,6 +113,7 @@ const deploy = { checkDownloads ({ version }) { const systems = [ { platform: 'linux', arch: 'x64' }, + { platform: 'linux', arch: 'arm64' }, { platform: 'darwin', arch: 'x64' }, { platform: 'darwin', arch: 'arm64' }, { platform: 'win32', arch: 'x64' }, diff --git a/scripts/binary/move-binaries.ts b/scripts/binary/move-binaries.ts index eb677e01a4ff..2b4a8a67e243 100644 --- a/scripts/binary/move-binaries.ts +++ b/scripts/binary/move-binaries.ts @@ -34,7 +34,7 @@ type semver = string /** * Platform plus architecture string like "darwin-x64" */ -type platformArch = 'darwin-x64' | 'darwin-arm64' | 'linux-x64' | 'win32-x64' +type platformArch = 'darwin-x64' | 'darwin-arm64' | 'linux-x64' | 'linux-arm64' | 'win32-x64' interface ReleaseInformation { commit: commit diff --git a/scripts/binary/upload.js b/scripts/binary/upload.js index 5eeabccb7ac6..479b97343e7f 100644 --- a/scripts/binary/upload.js +++ b/scripts/binary/upload.js @@ -87,6 +87,7 @@ module.exports = { 'darwin-x64': getUrl('darwin-x64'), 'darwin-arm64': getUrl('darwin-arm64'), 'linux-x64': getUrl('linux-x64'), + 'linux-arm64': getUrl('linux-arm64'), 'win32-x64': getUrl('win32-x64'), }, } diff --git a/scripts/binary/util/upload.js b/scripts/binary/util/upload.js index c776a9f0300e..238013ddd31b 100644 --- a/scripts/binary/util/upload.js +++ b/scripts/binary/util/upload.js @@ -1,4 +1,3 @@ -const _ = require('lodash') const awspublish = require('gulp-awspublish') const human = require('human-interval') const la = require('lazy-ass') @@ -112,7 +111,7 @@ const purgeDesktopAppAllPlatforms = function (version, zipName) { } // all architectures we are building the test runner for -const validPlatformArchs = ['darwin-arm64', 'darwin-x64', 'linux-x64', 'win32-x64'] +const validPlatformArchs = ['darwin-arm64', 'darwin-x64', 'linux-x64', 'linux-arm64', 'win32-x64'] // simple check for platform-arch string // example: isValidPlatformArch("darwin") // FALSE const isValidPlatformArch = check.oneOf(validPlatformArchs) @@ -122,25 +121,12 @@ const getValidPlatformArchs = () => { } const getUploadNameByOsAndArch = function (platform) { - // just hard code for now... const arch = os.arch() - const uploadNames = { - darwin: { - 'arm64': 'darwin-arm64', - 'x64': 'darwin-x64', - }, - linux: { - 'x64': 'linux-x64', - }, - win32: { - 'x64': 'win32-x64', - }, - } - const name = _.get(uploadNames[platform], arch) + const name = [platform, arch].join('-') - if (!name) { - throw new Error(`Cannot find upload name for OS: '${platform}' with arch: '${arch}'`) + if (!isValidPlatformArch(name)) { + throw new Error(`${name} is not a valid upload destination. Does validPlatformArchs need updating?`) } la(isValidPlatformArch(name), 'formed invalid platform', name, 'from', platform, arch) diff --git a/scripts/check-terminal.js b/scripts/check-terminal.js index 4b1e8f94b09b..0a12b122d037 100644 --- a/scripts/check-terminal.js +++ b/scripts/check-terminal.js @@ -1,9 +1,9 @@ // checks if the terminal has all the variables set (especially on Linux Docker) const assert = require('assert') -const isLinux = process.platform === 'linux' +const isMainLinux = process.platform === 'linux' && process.arch === 'x64' -if (isLinux) { +if (isMainLinux) { assert.ok(process.env.TERM === 'xterm', `process.env.TERM=${process.env.TERM} and must be set to "xterm" for Docker to work`) } diff --git a/yarn.lock b/yarn.lock index 079c123d5941..769fa2b0b3d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16233,10 +16233,10 @@ electron-to-chromium@^1.3.247, electron-to-chromium@^1.4.84: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.111.tgz#897613f6504f3f17c9381c7499a635b413e4df4e" integrity sha512-/s3+fwhKf1YK4k7btOImOzCQLpUjS6MaPf0ODTNuT4eTM1Bg4itBpLkydhOzJmpmH6Z9eXFyuuK5czsmzRzwtw== -electron@18.0.4: - version "18.0.4" - resolved "https://registry.yarnpkg.com/electron/-/electron-18.0.4.tgz#7b9b094db32805d4a7826d9f7a1b376d9d7b9f86" - integrity sha512-xfsozNpFr3WzeM1EFlw2qqiqXbCrgQNBJJMlcC4/DUYVpkF8364SZenX7FFFA42NmwXiOEahkvvho/u7UrAcGg== +electron@18.3.0: + version "18.3.0" + resolved "https://registry.npmjs.org/electron/-/electron-18.3.0.tgz#43de95979341e63f1b209c569a0ad148d98ae5b7" + integrity sha512-2+pAUIViVvFOGE5mJKKi8F6ruyvQrcqdfsm/AUfz+6P05vbvR5ZsR6WBkr90mlyojHW5w/nAVX9ZSOtz3aHs4A== dependencies: "@electron/get" "^1.13.0" "@types/node" "^16.11.26"