Skip to content

Commit

Permalink
Read changed files from TeamCity
Browse files Browse the repository at this point in the history
  • Loading branch information
noahtallen committed May 27, 2021
1 parent b2ded6a commit 362bf2e
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .teamcity/settings.kts
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ object SmartBuildLauncher : BuildType({
bashNodeScript {
name = "Launch relevant builds"
scriptContent = """
node ./packages/dependency-finder/dist/esm/index.js
node ./packages/dependency-finder/dist/esm/index.js --changedFiles %teamcity.build.changedFiles.file%
"""
}
}
Expand Down
1 change: 1 addition & 0 deletions package-map.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
{ "path": "packages/wpcom.js" },
{
"path": "apps/editing-toolkit",
"buildIds": [ "calypso_WPComPlugins_EditorToolKit" ],
"additionalEntryPoints": [
"apps/editing-toolkit/editing-toolkit-plugin/block-inserter-modifications/contextual-tips.js",
"apps/editing-toolkit/editing-toolkit-plugin/block-patterns/index.ts",
Expand Down
3 changes: 2 additions & 1 deletion packages/dependency-finder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"jest-config": "^26.6.3",
"read-pkg": "^6.0.0",
"read-pkg-up": "^8.0.0",
"tslib": "^2.2.0"
"tslib": "^2.2.0",
"yargs": "^17.0.1"
},
"scripts": {
"clean": "tsc --build ./tsconfig.json --clean && npx rimraf dist",
Expand Down
94 changes: 85 additions & 9 deletions packages/dependency-finder/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,22 @@
*/
import path from 'path';
import fs from 'fs';
import { readFile } from 'fs/promises';
import { fileURLToPath } from 'url';
import childProcess from 'child_process';
import { promisify } from 'util';
import yargs from 'yargs';

const args = yargs( process.argv.slice( 2 ) )
.usage( 'Usage: $0' )
.option( 'changedFiles', {
describe: 'A path to the list of VCS changed files',
type: 'string',
} )
.option( 'package', {
describe: 'The package to process from package-map.json',
type: 'string',
} ).argv;

const exec = promisify( childProcess.exec );

Expand All @@ -22,9 +35,9 @@ const packageMapPath = path.join(
type PackageMapEntry = {
path: string;
additionalEntryPoints: Array< string > | undefined;
buildIds: Array< string > | undefined;
};
type PackageMap = Array< PackageMapEntry >;
const packageMap: PackageMap = JSON.parse( fs.readFileSync( packageMapPath, 'utf8' ) );

function getMonorepoPackages() {
const packages = fs.readdirSync( 'packages', { withFileTypes: true } );
Expand All @@ -34,10 +47,13 @@ function getMonorepoPackages() {
}
const monorepoPackages = getMonorepoPackages();

const findPackageDependencies = async ( {
path: pkgPath,
additionalEntryPoints,
}: PackageMapEntry ) => {
type Project = {
matchingFiles: Array< string >;
buildIds: Array< string > | undefined;
};

const findPackageDependencies = async ( entry: PackageMapEntry ): Promise< Project > => {
const { path: pkgPath, additionalEntryPoints } = entry;
const absolutePkgPath = path.resolve( pkgPath );

const { missing, packages, modules } = await findDependencies( {
Expand Down Expand Up @@ -68,15 +84,75 @@ const findPackageDependencies = async ( {
console.log( unknownFiles.length ? unknownFiles.map( ( m ) => ' ' + m ).join( '\n' ) : ' -' );
console.log();
console.log();
return {
...entry,
matchingFiles: modules,
};
};

/**
* Given a list of currently modified files, returns the CI jobs which need to be
* launched.
*
* TODO: improve algorithmic complexity. Currently O(modifiedFiles * projects * matchingFiles).
*
* @param projects The list of projects.
* @param modifiedFiles The list of currently modified files.
* @returns A Set of CI Job IDs to launch.
*/
// TODO: make sure project files are relative to repo root.
function findMatchingBuilds( projects: Project[], modifiedFiles: VCSFileChange[] ) {
return modifiedFiles.reduce< Set< string > >( ( acc, modifiedFile ) => {
const matchingProject = projects.find( ( proj ) =>
proj.matchingFiles.some( ( file ) => file === modifiedFile.path )
);
if ( matchingProject?.buildIds ) {
matchingProject.buildIds.forEach( ( id ) => acc.add( id ) );
}
return acc;
}, new Set() );
}

// <relative file path>:<change type>:<revision>
// see https://plugins.jetbrains.com/docs/teamcity/risk-tests-reordering-in-custom-test-runner.html
type VCSFileChange = {
path: string;
changeType:
| 'CHANGED'
| 'ADDED'
| 'REMOVED'
| 'NOT_CHANGED'
| 'DIRECTORY_CHANGED'
| 'DIRECTORY_ADDED'
| 'DIRECTORY_REMOVED';
revision: string;
};
async function readTeamCityMatchedFiles( filePath: string ) {
const rawContents = await readFile( filePath, 'utf8' );
return rawContents.split( '\n' ).map( ( entry ) => {
const [ path, changeType, revision ] = entry.split( ':' );
return {
path,
changeType,
revision,
} as VCSFileChange;
} );
}

const main = async () => {
const packageToParse = process.argv.slice( 2 )[ 0 ];
const packageMap: PackageMap = JSON.parse( await readFile( packageMapPath, 'utf8' ) );
if ( args.changedFiles ) {
const changedFiles = await readTeamCityMatchedFiles( args.changedFiles );
console.log( changedFiles );
return;
}

// Allow parsing a single package.
if ( packageToParse ) {
const packageEntry = packageMap.find( ( { path } ) => path === packageToParse );
if ( args.package ) {
const packageEntry = packageMap.find( ( { path } ) => path === args.package );
if ( packageEntry ) {
findPackageDependencies( packageEntry );
const project = await findPackageDependencies( packageEntry );
findMatchingBuilds( [ project ], [] );
}
} else {
for ( const packageEntry of packageMap ) {
Expand Down

0 comments on commit 362bf2e

Please sign in to comment.