Skip to content

Commit

Permalink
Cleanup Folders after codegen (#33891)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #33891

This diff adds a function to clean up folders and empty files (if any) after the codegen runs in the OSS.
To align how iOS and Android behave in the codegen, we now have to preemptively create folders that we may not need later.
Thanks to this function, we recursively the folders in the codegen and we delete them if they are empty.

## Changelog
[iOS][Added] - Add function to cleanup codegen folders

Reviewed By: cortinico

Differential Revision: D36594999

fbshipit-source-id: 904aa2442c0d9621b7ec77c31f7be9daa4e7b7e1
  • Loading branch information
Riccardo Cipolleschi authored and facebook-github-bot committed May 23, 2022
1 parent 5f3c5aa commit 7169288
Show file tree
Hide file tree
Showing 2 changed files with 260 additions and 0 deletions.
225 changes: 225 additions & 0 deletions scripts/codegen/__tests__/generate-artifacts-executor-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,228 @@ describe('extractLibrariesFromJSON', () => {
});
});
});

describe('delete empty files and folders', () => {
beforeEach(() => {
jest.resetModules();
});

it('when path is empty file, deletes it', () => {
const targetFilepath = 'my-file.txt';
let statSyncInvocationCount = 0;
let rmSyncInvocationCount = 0;
let rmdirSyncInvocationCount = 0;
jest.mock('fs', () => ({
statSync: filepath => {
statSyncInvocationCount += 1;
expect(filepath).toBe(targetFilepath);
return {
isFile: () => {
return true;
},
size: 0,
};
},
rmSync: filepath => {
rmSyncInvocationCount += 1;
expect(filepath).toBe(targetFilepath);
},
rmdirSync: filepath => {
rmdirSyncInvocationCount += 1;
},
}));

underTest._cleanupEmptyFilesAndFolders(targetFilepath);
expect(statSyncInvocationCount).toBe(1);
expect(rmSyncInvocationCount).toBe(1);
expect(rmdirSyncInvocationCount).toBe(0);
});

it('when path is not an empty file, does nothing', () => {
const targetFilepath = 'my-file.txt';
const size = 128;

let statSyncInvocationCount = 0;
let rmSyncInvocationCount = 0;
let rmdirSyncInvocationCount = 0;

jest.mock('fs', () => ({
statSync: filepath => {
statSyncInvocationCount += 1;
expect(filepath).toBe(targetFilepath);
return {
isFile: () => {
return true;
},
size: size,
};
},
rmSync: filepath => {
rmSyncInvocationCount += 1;
},
rmdirSync: filepath => {
rmdirSyncInvocationCount += 1;
},
}));

underTest._cleanupEmptyFilesAndFolders(targetFilepath);
expect(statSyncInvocationCount).toBe(1);
expect(rmSyncInvocationCount).toBe(0);
expect(rmdirSyncInvocationCount).toBe(0);
});

it("when path is folder and it's empty, removes it", () => {
const targetFolder = 'build/';
const content = [];

let statSyncInvocationCount = 0;
let readdirInvocationCount = 0;
let rmSyncInvocationCount = 0;
let rmdirSyncInvocationCount = 0;

jest.mock('fs', () => ({
statSync: filepath => {
statSyncInvocationCount += 1;
expect(filepath).toBe(targetFolder);
return {
isFile: () => {
return false;
},
};
},
rmSync: filepath => {
rmSyncInvocationCount += 1;
},
rmdirSync: filepath => {
rmdirSyncInvocationCount += 1;
expect(filepath).toBe(targetFolder);
},
readdirSync: filepath => {
readdirInvocationCount += 1;
return content;
},
}));

underTest._cleanupEmptyFilesAndFolders(targetFolder);
expect(statSyncInvocationCount).toBe(1);
expect(readdirInvocationCount).toBe(2);
expect(rmSyncInvocationCount).toBe(0);
expect(rmdirSyncInvocationCount).toBe(1);
});

it("when path is folder and it's not empty, removes only empty folders and files", () => {
const targetFolder = 'build/';
const content = ['emptyFolder', 'emptyFile', 'notEmptyFile'];

const files = ['build/emptyFile', 'build/notEmptyFile'];

const emptyContent = [];
const fileSizes = {
'build/emptyFile': 0,
'build/notEmptyFile': 32,
};

let statSyncInvocation = [];
let rmSyncInvocation = [];
let rmdirSyncInvocation = [];
let readdirInvocation = [];

jest.mock('fs', () => ({
statSync: filepath => {
statSyncInvocation.push(filepath);

return {
isFile: () => {
return files.includes(filepath);
},
size: fileSizes[filepath],
};
},
rmSync: filepath => {
rmSyncInvocation.push(filepath);
},
rmdirSync: filepath => {
rmdirSyncInvocation.push(filepath);
},
readdirSync: filepath => {
readdirInvocation.push(filepath);
return filepath === targetFolder ? content : emptyContent;
},
}));

underTest._cleanupEmptyFilesAndFolders(targetFolder);
expect(statSyncInvocation).toEqual([
'build/',
'build/emptyFolder',
'build/emptyFile',
'build/notEmptyFile',
]);
expect(readdirInvocation).toEqual([
'build/',
'build/emptyFolder',
'build/emptyFolder',
'build/',
]);
expect(rmSyncInvocation).toEqual(['build/emptyFile']);
expect(rmdirSyncInvocation).toEqual(['build/emptyFolder']);
});

it('when path is folder and it contains only empty folders, removes everything', () => {
const targetFolder = 'build/';
const content = ['emptyFolder1', 'emptyFolder2'];
const emptyContent = [];

let statSyncInvocation = [];
let rmSyncInvocation = [];
let rmdirSyncInvocation = [];
let readdirInvocation = [];

jest.mock('fs', () => ({
statSync: filepath => {
statSyncInvocation.push(filepath);

return {
isFile: () => {
return false;
},
};
},
rmSync: filepath => {
rmSyncInvocation.push(filepath);
},
rmdirSync: filepath => {
rmdirSyncInvocation.push(filepath);
},
readdirSync: filepath => {
readdirInvocation.push(filepath);
return filepath === targetFolder
? content.filter(
element =>
!rmdirSyncInvocation.includes(path.join(targetFolder, element)),
)
: emptyContent;
},
}));

underTest._cleanupEmptyFilesAndFolders(targetFolder);
expect(statSyncInvocation).toEqual([
'build/',
'build/emptyFolder1',
'build/emptyFolder2',
]);
expect(readdirInvocation).toEqual([
'build/',
'build/emptyFolder1',
'build/emptyFolder1',
'build/emptyFolder2',
'build/emptyFolder2',
'build/',
]);
expect(rmSyncInvocation).toEqual([]);
expect(rmdirSyncInvocation).toEqual([
'build/emptyFolder1',
'build/emptyFolder2',
'build/',
]);
});
});
35 changes: 35 additions & 0 deletions scripts/codegen/generate-artifacts-executor.js
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,39 @@ function createComponentProvider(
}
}

// It removes all the empty files and empty folders
// it finds, starting from `filepath`, recursively.
//
// This function is needed since, after aligning the codegen between
// iOS and Android, we have to create empty folders in advance and
// we don't know wheter they will be populated up until the end of the process.
//
// @parameter filepath: the root path from which we want to remove the empty files and folders.
function cleanupEmptyFilesAndFolders(filepath) {
const stats = fs.statSync(filepath);

if (stats.isFile() && stats.size === 0) {
fs.rmSync(filepath);
return;
} else if (stats.isFile()) {
return;
}

const dirContent = fs.readdirSync(filepath);
dirContent.forEach(contentPath =>
cleanupEmptyFilesAndFolders(path.join(filepath, contentPath)),
);

// The original folder may be filled with empty folders
// if that the case, we would also like to remove the parent.
// Hence, we need to read the folder again.
const newContent = fs.readdirSync(filepath);
if (newContent.length === 0) {
fs.rmdirSync(filepath);
return;
}
}

// Execute

/**
Expand Down Expand Up @@ -428,6 +461,7 @@ function execute(
);

createComponentProvider(fabricEnabled, schemaPaths, node, iosOutputDir);
cleanupEmptyFilesAndFolders(iosOutputDir);
} catch (err) {
console.error(err);
process.exitCode = 1;
Expand All @@ -443,4 +477,5 @@ module.exports = {
_extractLibrariesFromJSON: extractLibrariesFromJSON,
_executeNodeScript: executeNodeScript,
_generateCode: generateCode,
_cleanupEmptyFilesAndFolders: cleanupEmptyFilesAndFolders,
};

0 comments on commit 7169288

Please sign in to comment.