2020-10-30 20:34:04 +00:00
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
2020-09-16 16:40:15 +00:00
const assert = require ( 'assert' ) ;
const fs = require ( 'fs' ) ;
const { join } = require ( 'path' ) ;
const pMap = require ( 'p-map' ) ;
const prettier = require ( 'prettier' ) ;
2024-03-01 19:33:00 +00:00
// During development, you might use local versions of dependencies which are missing
// acknowledgment files. In this case we'll skip rebuilding the acknowledgment files.
// Enable this flag to throw an error.
const REQUIRE _SIGNAL _LIB _FILES = Boolean ( process . env . REQUIRE _SIGNAL _LIB _FILES ) ;
2020-09-16 16:40:15 +00:00
const {
dependencies = { } ,
optionalDependencies = { } ,
} = require ( '../package.json' ) ;
2024-03-01 01:06:18 +00:00
const SIGNAL _LIBS = [ '@signalapp/libsignal-client' , '@signalapp/ringrtc' ] ;
const SKIPPED _DEPENDENCIES = new Set ( SIGNAL _LIBS ) ;
2020-09-16 16:40:15 +00:00
const rootDir = join ( _ _dirname , '..' ) ;
const nodeModulesPath = join ( rootDir , 'node_modules' ) ;
const destinationPath = join ( rootDir , 'ACKNOWLEDGMENTS.md' ) ;
function isLicenseFileName ( fileName ) {
return /^licen[s|c]e/i . test ( fileName ) ;
}
async function getMarkdownForDependency ( dependencyName ) {
let licenseBody ;
// fs-xattr is an optional dependency that may fail to install (on Windows, most
// commonly), so we have a special case for it here. We may need to do something
// similar for new optionalDependencies in the future.
if ( dependencyName === 'fs-xattr' ) {
licenseBody = 'License: MIT' ;
} else {
const dependencyRootPath = join ( nodeModulesPath , dependencyName ) ;
const licenseFileName = (
await fs . promises . readdir ( dependencyRootPath )
) . find ( isLicenseFileName ) ;
if ( licenseFileName ) {
const licenseFilePath = join ( dependencyRootPath , licenseFileName ) ;
licenseBody = (
await fs . promises . readFile ( licenseFilePath , 'utf8' )
) . trim ( ) ;
} else {
const packageJsonPath = join ( dependencyRootPath , 'package.json' ) ;
const { license } = JSON . parse (
await fs . promises . readFile ( packageJsonPath )
) ;
if ( ! license ) {
throw new Error ( ` Could not find license for ${ dependencyName } ` ) ;
}
licenseBody = ` License: ${ license } ` ;
}
}
return [
` ## ${ dependencyName } ` ,
'' ,
... licenseBody . split ( /\r?\n/ ) . map ( line => {
const trimmed = line . trim ( ) ;
if ( trimmed ) {
return ` ${ trimmed } ` ;
}
return trimmed ;
} ) ,
] . join ( '\n' ) ;
}
2024-03-01 01:06:18 +00:00
async function getMarkdownForSignalLib ( dependencyName ) {
const dependencyRootPath = join ( nodeModulesPath , dependencyName ) ;
const licenseFilePath = join (
dependencyRootPath ,
'dist' ,
'acknowledgments.md'
) ;
2024-03-01 19:33:00 +00:00
let licenseBody ;
try {
licenseBody = await fs . promises . readFile ( licenseFilePath , 'utf8' ) ;
} catch ( err ) {
if ( err ) {
if ( err . code === 'ENOENT' && ! REQUIRE _SIGNAL _LIB _FILES ) {
console . warn (
` Missing acknowledgments file for ${ dependencyName } . Skipping generation of acknowledgments. `
) ;
process . exit ( 0 ) ;
}
throw err ;
}
}
return [
` # Acknowledgements for ${ dependencyName } ` ,
'' ,
licenseBody . replace ( /^# Acknowledgments/ , '' ) . trim ( ) ,
] . join ( '\n' ) ;
2024-03-01 01:06:18 +00:00
}
2020-09-16 16:40:15 +00:00
async function main ( ) {
assert . deepStrictEqual (
Object . keys ( optionalDependencies ) ,
[ 'fs-xattr' ] ,
'Unexpected optionalDependencies when generating acknowledgments file. To ensure that this file is generated deterministically, make sure to special-case it the acknowledgments generation script.'
) ;
const dependencyNames = [
... Object . keys ( dependencies ) ,
... Object . keys ( optionalDependencies ) ,
]
. filter ( name => ! SKIPPED _DEPENDENCIES . has ( name ) )
. sort ( ) ;
const markdownsForDependency = await pMap (
dependencyNames ,
getMarkdownForDependency ,
// Without this, we may run into "too many open files" errors.
2020-09-18 20:40:41 +00:00
{
concurrency : 100 ,
timeout : 1000 * 60 * 2 ,
}
2020-09-16 16:40:15 +00:00
) ;
2024-03-01 01:06:18 +00:00
// For our libraries copy the respective acknowledgement lists
const markdownsFromSignalLibs = await pMap (
SIGNAL _LIBS ,
getMarkdownForSignalLib ,
{
concurrency : 100 ,
timeout : 1000 * 60 * 2 ,
}
) ;
2020-09-16 16:40:15 +00:00
const unformattedOutput = [
2023-01-03 19:55:46 +00:00
'<!-- Copyright 2020 Signal Messenger, LLC -->' ,
'<!-- SPDX-License-Identifier: AGPL-3.0-only -->' ,
2020-09-16 16:40:15 +00:00
'# Acknowledgments' ,
'' ,
'Signal Desktop makes use of the following open source projects.' ,
'' ,
markdownsForDependency . join ( '\n\n' ) ,
2024-02-26 23:08:47 +00:00
'' ,
'## Kyber Patent License' ,
'' ,
'<https://csrc.nist.gov/csrc/media/Projects/post-quantum-cryptography/documents/selected-algos-2022/nist-pqc-license-summary-and-excerpts.pdf>' ,
2024-03-01 01:06:18 +00:00
'' ,
markdownsFromSignalLibs . join ( '\n\n' ) ,
2020-09-16 16:40:15 +00:00
] . join ( '\n' ) ;
const prettierConfig = await prettier . resolveConfig ( destinationPath ) ;
const output = prettier . format ( unformattedOutput , {
... prettierConfig ,
filepath : destinationPath ,
} ) ;
await fs . promises . writeFile ( destinationPath , output ) ;
}
main ( ) . catch ( err => {
console . error ( err ) ;
process . exit ( 1 ) ;
} ) ;