Skip to content

Commit

Permalink
feat: allow only using macOS SDKs in CI
Browse files Browse the repository at this point in the history
  • Loading branch information
codebytere committed Jun 6, 2024
1 parent 0b3bd7e commit 2f871ce
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 6 deletions.
12 changes: 9 additions & 3 deletions src/e
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const depot = require('./utils/depot-tools');
const goma = require('./utils/goma');
const { refreshPathVariable } = require('./utils/refresh-path');
const { loadXcode } = require('./utils/load-xcode');
const { ensureSDK } = require('./utils/sdk');

// Refresh the PATH variable at the top of this shell so that retries in the same shell get the latest PATH variable
refreshPathVariable();
Expand Down Expand Up @@ -180,11 +181,16 @@ ci.command('cancel', 'Cancel CI workflows and builds');

program
.command('load-xcode')
.option('--only-sdk', 'Only load the macOS SDK and not the full Xcode', false)
.description(
'Loads required versions of Xcode and the macOS SDK and symlinks them (may require sudo)',
'Loads required versions of Xcode or macOS SDKs and symlinks them (may require sudo)',
)
.action(() => {
loadXcode();
.action(({ onlySdk }) => {
if (onlySdk) {
ensureSDK();
} else {
loadXcode();
}
});

program
Expand Down
7 changes: 6 additions & 1 deletion src/e-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const goma = require('./utils/goma');
const { ensureDir } = require('./utils/paths');
const reclient = require('./utils/reclient');
const { loadXcode } = require('./utils/load-xcode');
const { ensureSDK } = require('./utils/sdk');

function runGNGen(config) {
depot.ensure();
Expand Down Expand Up @@ -102,7 +103,11 @@ program
}

if (process.platform === 'darwin') {
loadXcode({ target, quiet: true });
if (process.env.CI) {
ensureSDK();
} else {
loadXcode({ target, quiet: true });
}
}

if (options.gen) {
Expand Down
8 changes: 6 additions & 2 deletions src/e-init.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const { URI } = require('vscode-uri');
const evmConfig = require('./evm-config');
const { color, fatal } = require('./utils/logging');
const { resolvePath, ensureDir } = require('./utils/paths');
const reclient = require('./utils/reclient');
const depot = require('./utils/depot-tools');
const { checkGlobalGitConfig } = require('./utils/git');
const { loadXcode } = require('./utils/load-xcode');
Expand Down Expand Up @@ -139,6 +138,7 @@ program
.option('--msan', `When building, enable clang's memory sanitizer`, false)
.option('--lsan', `When building, enable clang's leak sanitizer`, false)
.option('--mas', 'Build for the macOS App Store', false)
.option('--use-sdk', 'Use macOS SDKs instead of downloading full XCode versions.')
.addOption(archOption)
.option('--bootstrap', 'Run `e sync` and `e build` after creating the build config.')
.addOption(
Expand Down Expand Up @@ -196,7 +196,11 @@ program

// ensure xcode is loaded
if (process.platform === 'darwin') {
loadXcode(true);
if (process.env.CI || options.useSdk) {
ensureSDK();
} else {
loadXcode({ target, quiet: true });
}
}

ensureRoot(config, !!options.force);
Expand Down
10 changes: 10 additions & 0 deletions src/evm-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,15 @@ function validateConfig(config) {
}
}

function setEnvVar(name, key, value) {
const config = loadConfigFileRaw(name);

config.env = config.env || {};
config.env[key] = value;

save(name, config);
}

function sanitizeConfig(name, config, overwrite = false) {
const changes = [];

Expand Down Expand Up @@ -332,5 +341,6 @@ module.exports = {
sanitizeConfigWithName,
save,
setCurrent,
setEnvVar,
validateConfig,
};
181 changes: 181 additions & 0 deletions src/utils/sdk.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
const cp = require('child_process');
const fs = require('fs');
const path = require('path');
const semver = require('semver');
const { ensureDir } = require('./paths');
const evmConfig = require('../evm-config');

const { color, fatal } = require('./logging');
const { deleteDir } = require('./paths');

const SDKDir = path.resolve(__dirname, '..', '..', 'third_party', 'SDKs');
const SDKZip = path.resolve(SDKDir, 'MacOSX.sdk.zip');

const XcodeBaseURL = 'https://dev-cdn.electronjs.org/xcode/';

const SDKs = {
'14.0': {
fileName: 'MacOSX-14.0.sdk.zip',
sha256: '63c0e69c60c3e0f25b42726fc3a0f0f2902b5cac304d78456c33e13f9dd6d081',
},
'13.3': {
fileName: 'MacOSX-13.3.sdk.zip',
sha256: '78b99db2e6f0fba2ffeef0d2dfb25e6b700483e3f18cdaf9a18a9a3f9ed10cfa',
},
'13.0': {
fileName: 'MacOSX-13.0.sdk.zip',
sha256: '689ae09a4287d2711bd137e48a675635e0d66fdcfb3cf8a324c4b3f03c2cf883',
},
'12.3': {
fileName: 'MacOSX-12.3.sdk.zip',
sha256: '4fb95f95d439f1d1eaf5c89e3e2b842ac746b96273908f8bf8d732a39597e78f',
},
};

const fallbackSDK = () => {
return Object.keys(SDKs)
.map(v => semver.valid(semver.coerce(v)))
.sort(semver.rcompare)[0];
};

function getSDKVersion() {
const { SDKROOT } = evmConfig.current().env;
console.log(SDKROOT);

if (!fs.existsSync(SDKROOT)) {
return 'unknown';
}

const settingsPath = path.resolve(SDKROOT, 'SDKSettings.json');
const data = fs.readFileSync(settingsPath, 'utf8');
const json = JSON.parse(data);

return json.MinimalDisplayName;
}

function extractSDKVersion(config) {
const match = /macOS \d+(?:\.\d+)? SDK\n\# \((\d+\.\d+)/.exec(config);
return match ? match[1] : null;
}

function expectedSDKVersion() {
const { root } = evmConfig.current();

// The current Xcode version and associated SDK can be found in build/mac_toolchain.py.
const macToolchainPy = path.resolve(root, 'src', 'build', 'mac_toolchain.py');

if (!fs.existsSync(macToolchainPy)) {
console.warn(
color.warn,
`Failed to find ${color.path(macToolchainPy)} - falling back to default of`,
fallbackSDK(),
);
return fallbackSDK();
}

const config = fs.readFileSync(macToolchainPy, 'utf8');
const version = extractSDKVersion(config);

if (isNaN(Number(version)) || !SDKs[version]) {
console.warn(
color.warn,
`Automatically detected an unknown macOS SDK ${color.path(
version,
)} - falling back to default of`,
fallbackSDK(),
);
return fallbackSDK();
}

return version;
}

function ensureSDK() {
const expected = expectedSDKVersion();
const eventualVersionedPath = path.resolve(SDKDir, `MacOSX-${expected}.sdk`);

const shouldEnsureSDK = !fs.existsSync(eventualVersionedPath) || getSDKVersion() !== expected;

if (shouldEnsureSDK) {
ensureDir(SDKDir);
const expectedSDKHash = SDKs[expected].sha256;

if (!fs.existsSync(eventualVersionedPath)) {
let shouldDownload = true;
if (fs.existsSync(SDKZip)) {
const existingHash = hashFile(SDKZip);
if (existingHash === expectedSDKHash) {
shouldDownload = false;
} else {
console.log(
`${color.warn} Got existing hash ${color.cmd(
existingHash,
)} which did not match ${color.cmd(expectedSDKHash)} so redownloading SDK`,
);
deleteDir(SDKZip);
}
}

if (shouldDownload) {
const sdkURL = `${XcodeBaseURL}${SDKs[expected].fileName}`;
console.log(`Downloading ${color.cmd(sdkURL)} into ${color.path(SDKZip)}`);
const { status } = cp.spawnSync(
process.execPath,
[path.resolve(__dirname, '..', 'download.js'), sdkURL, SDKZip],
{
stdio: 'inherit',
},
);

if (status !== 0) {
deleteDir(SDKZip);
fatal(`Failure while downloading SDK zip`);
}

const newHash = hashFile(SDKZip);
if (newHash !== expectedSDKHash) {
deleteDir(SDKZip);
fatal(
`Downloaded SDK zip had hash "${newHash}" which does not match expected hash "${expectedSDKHash}"`,
);
}
}

console.log(`Extracting ${color.cmd(SDKZip)} into ${color.path(eventualVersionedPath)}`);
const unzipPath = path.resolve(SDKDir, 'tmp_unzip');
deleteDir(unzipPath);
const { status } = cp.spawnSync('unzip', ['-q', '-o', SDKZip, '-d', unzipPath], {
stdio: 'inherit',
});
if (status !== 0) {
fatal('Failure while extracting SDK zip');
}

fs.renameSync(path.resolve(unzipPath, 'MacOSX.sdk'), eventualVersionedPath);
deleteDir(SDKZip);
deleteDir(unzipPath);
}

evmConfig.setEnvVar(evmConfig.currentName(), 'SDKROOT', eventualVersionedPath);
}

deleteDir(SDKZip);

console.log(`${color.info} Now using SDK version ${color.path(getSDKVersion())}`);

return true;
}

// Hash MacOSX.sdk directory zip with sha256.
function hashFile(file) {
console.log(`Calculating hash for ${color.path(file)}`);
return cp
.spawnSync('shasum', ['-a', '256', file])
.stdout.toString()
.split(' ')[0]
.trim();
}

module.exports = {
ensureSDK,
};

0 comments on commit 2f871ce

Please sign in to comment.