Guard native patch targets and document verified WebRTC backport preparation
This commit is contained in:
1 parent
09bf616d98
commit
7925633527
3 files changed
+241
No files matched your search
@@ -0,0 +1,87 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import {execFile} from 'node:child_process';
|
||||
import {promisify} from 'node:util';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
|
||||
const execute=promisify(execFile);
|
||||
const FONTCONFIG='src/third_party/fontconfig/src';
|
||||
function inside(root,target){const relative=path.relative(root,target);return relative===''||(!relative.startsWith(`..${path.sep}`)&&relative!=='..'&&!path.isAbsolute(relative));}
|
||||
async function contained(root,target){
|
||||
const resolved=await fs.realpath(target);
|
||||
if(!inside(root,resolved))throw Error('path-outside-checkout');
|
||||
return resolved;
|
||||
}
|
||||
async function nearestContained(root,target){
|
||||
if(!inside(root,target))throw Error('path-outside-checkout');
|
||||
let current=target;
|
||||
while(true){try{return await contained(root,current);}catch(error){if(error.code!=='ENOENT')throw error;const parent=path.dirname(current);if(parent===current)throw error;current=parent;}}
|
||||
}
|
||||
async function git(repo,args){
|
||||
const {stdout}=await execute('git',['--no-optional-locks','-C',repo,...args],{env:{...process.env,GIT_OPTIONAL_LOCKS:'0'},timeout:120000,maxBuffer:8*1024*1024,windowsHide:true});
|
||||
return stdout.trim();
|
||||
}
|
||||
|
||||
// Read-only prerequisite check. In particular, an existing directory is not
|
||||
// proof of an independent checkout: Git silently searches ancestor repositories.
|
||||
export async function nativePatchPreflight({checkoutRoot,configPath,excludeWindowsFontconfig=false,platform=process.platform}){
|
||||
const result={scope:'Read-only patch target preflight; not patch import or build proof.',passed:false,required:[],excluded:[],errors:[]};
|
||||
try{
|
||||
const root=await fs.realpath(checkoutRoot);result.checkoutRoot=root;
|
||||
const config=await contained(root,path.resolve(configPath));
|
||||
const targets=JSON.parse(await fs.readFile(config,'utf8'));
|
||||
if(!Array.isArray(targets)||targets.length===0||targets.length>64)throw Error('invalid-patch-config');
|
||||
const seen=new Set();
|
||||
for(const target of targets){
|
||||
const row={repo:target?.repo,patchDir:target?.patch_dir,errors:[]};
|
||||
try{
|
||||
if(!target||typeof target.repo!=='string'||typeof target.patch_dir!=='string'||!target.repo||!target.patch_dir||path.isAbsolute(target.repo)||path.isAbsolute(target.patch_dir)||seen.has(target.repo))throw Error('invalid-patch-target');
|
||||
seen.add(target.repo);
|
||||
const repo=path.resolve(root,target.repo),patchDir=path.resolve(root,target.patch_dir);
|
||||
await nearestContained(root,repo);
|
||||
const patches=await contained(root,patchDir),list=await contained(root,path.join(patches,'.patches'));
|
||||
// Electron's official reader keeps every list line, including blanks.
|
||||
const text=await fs.readFile(list,'utf8');const names=text.split(/\r?\n/);if(names.at(-1)==='')names.pop();
|
||||
if(!names.length||names.some(name=>!name||path.isAbsolute(name)))throw Error('invalid-patch-list');
|
||||
for(const name of names){const file=await contained(root,path.resolve(patches,name));if(!inside(patches,file))throw Error('patch-outside-patch-directory');}
|
||||
row.patchCount=names.length;
|
||||
if(target.repo===FONTCONFIG&&excludeWindowsFontconfig){
|
||||
if(platform!=='win32')throw Error('fontconfig-exclusion-requires-windows');
|
||||
const deps=await fs.readFile(await contained(root,path.join(root,'src/DEPS')),'utf8');
|
||||
if(!/['"]src\/third_party\/fontconfig\/src['"]\s*:\s*\{[^}]*['"]condition['"]\s*:\s*['"]checkout_linux['"]/s.test(deps))throw Error('fontconfig-exclusion-condition-unverified');
|
||||
row.reason='Explicit Windows exclusion: Chromium DEPS checkout_linux';result.excluded.push(row);continue;
|
||||
}
|
||||
const own=await contained(root,repo);
|
||||
try{await contained(root,path.join(own,'.git'));}catch(error){if(error.code==='ENOENT')throw Error('missing-own-git-marker',{cause:error});throw error;}
|
||||
const top=await git(own,['rev-parse','--show-toplevel']);
|
||||
if(await fs.realpath(top)!==own)throw Error('ancestor-git-root');
|
||||
const gitDir=await git(own,['rev-parse','--absolute-git-dir']);await contained(root,gitDir);
|
||||
const am=path.resolve(own,await git(own,['rev-parse','--git-path','rebase-apply']));
|
||||
await nearestContained(root,am);
|
||||
try{await fs.stat(am);throw Error('git-am-in-progress');}catch(error){if(error.code!=='ENOENT')throw error;}
|
||||
row.head=await git(own,['rev-parse','--verify','HEAD']);
|
||||
row.trackedClean=(await git(own,['status','--porcelain=v1','--untracked-files=no']))==='';
|
||||
if(!row.trackedClean)throw Error('tracked-worktree-not-clean');
|
||||
}catch(error){row.errors.push(error.code==='ENOENT'?'missing-required-path':error.message||'preflight-failed');}
|
||||
result.required.push(row);
|
||||
}
|
||||
result.errors=result.required.filter(row=>row.errors.length).map(row=>({repo:row.repo,errors:row.errors}));
|
||||
result.passed=result.errors.length===0;
|
||||
}catch(error){result.errors.push({errors:[error.code==='ENOENT'?'missing-required-path':error.message||'preflight-failed']});}
|
||||
result.requiredCount=result.required.length;result.excludedCount=result.excluded.length;
|
||||
return result;
|
||||
}
|
||||
|
||||
if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){
|
||||
const args=process.argv.slice(2);const allowed=new Set(['--checkout-root','--config','--exclude-windows-fontconfig']);let checkoutRoot,configPath,excludeWindowsFontconfig=false;
|
||||
try{
|
||||
for(let i=0;i<args.length;i++){
|
||||
if(!allowed.has(args[i]))throw Error('invalid-cli-argument');
|
||||
if(args[i]==='--exclude-windows-fontconfig'){excludeWindowsFontconfig=true;continue;}
|
||||
const value=args[++i];if(!value||value.startsWith('--'))throw Error('missing-cli-value');
|
||||
if(args[i-1]==='--checkout-root')checkoutRoot=value;else configPath=value;
|
||||
}
|
||||
if(!checkoutRoot||!configPath)throw Error('checkout-root-and-config-required');
|
||||
const result=await nativePatchPreflight({checkoutRoot,configPath,excludeWindowsFontconfig});console.log(JSON.stringify(result,null,2));process.exitCode=result.passed?0:1;
|
||||
}catch(error){console.log(JSON.stringify({passed:false,errors:[error.message]}));process.exitCode=1;}
|
||||
}
|
||||
Reference in new issue
Block a user