diff --git a/README.md b/README.md index 2a785749d..a824d718c 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ The CLI can build code for following targets: +- Android AAR files - Generic CommonJS build - ES modules build for bundlers such as webpack - Flow definitions (copies .js files to .flow files) @@ -40,6 +41,7 @@ To configure your project manually, follow these steps: "source": "src", "output": "lib", "targets": [ + ["aar", {"reverseJetify": true}], ["commonjs", {"flow": true}], "module", "typescript", diff --git a/package.json b/package.json index 3ffffca28..9992a9ac4 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,9 @@ "metro-react-native-babel-preset": "^0.53.1", "yargs": "^13.2.2" }, + "optionalDependencies": { + "jetifier": "^1.0.0-beta04.2" + }, "devDependencies": { "@babel/cli": "^7.2.3", "@babel/preset-env": "^7.4.2", diff --git a/src/cli.ts b/src/cli.ts index 3a324e7d7..a0481c934 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -6,6 +6,7 @@ import inquirer from 'inquirer'; import cosmiconfig from 'cosmiconfig'; import isGitDirty from 'is-git-dirty'; import * as logger from './utils/logger'; +import buildAAR from './targets/aar'; import buildCommonJS from './targets/commonjs'; import buildModule from './targets/module'; import buildTypescript from './targets/typescript'; @@ -78,7 +79,7 @@ yargs type: 'checkbox', name: 'targets', message: 'Which targets do you want to build?', - choices: ['commonjs', 'module', 'typescript'], + choices: ['aar', 'commonjs', 'module', 'typescript'], validate: input => Boolean(input.length), }, ]; @@ -286,6 +287,15 @@ yargs report.info(`Building target ${chalk.blue(targetName)}`); switch (targetName) { + case 'aar': + await buildAAR({ + root, + source: path.resolve(root, source as string), + output: path.resolve(root, output as string, 'aar'), + options: targetOptions, + report, + }); + break; case 'commonjs': await buildCommonJS({ root, diff --git a/src/targets/aar.ts b/src/targets/aar.ts new file mode 100644 index 000000000..409f32e62 --- /dev/null +++ b/src/targets/aar.ts @@ -0,0 +1,96 @@ +import path from 'path'; +import chalk from 'chalk'; +import fs from 'fs-extra'; +import del from 'del'; +import androidAssemble from '../utils/androidAssemble'; +import { Input } from '../types'; +import jetifier from '../utils/jetifier'; + +type TargetOptions = { + androidPath: string, + reverseJetify: boolean +}; + +const defaultOptions: TargetOptions = { + androidPath: "android", + reverseJetify: false +}; + +type Options = Input & { + options?: Partial; +}; + +async function createGradleFile(file: string) { + await fs.createFile(file); + await fs.writeFile(file, 'configurations.maybeCreate("default")\nartifacts.add("default", file(\'android.aar\'))') +} + +export default async function build({ + root, + output, + options, + report, +}: Options) { + const targetOptions = { + ...defaultOptions, + ...options + }; + + report.info( + `Cleaning up previous build at ${chalk.blue(path.relative(root, output))}` + ); + + await del([output]); + + await androidAssemble({ root, androidPath: targetOptions.androidPath, report }); + + report.info( + `Creating new output directory at ${chalk.blue(path.relative(root, output))}` + ); + await fs.mkdir(output); + + const sourceAar = path.join(targetOptions.androidPath, 'build', 'outputs', 'aar', 'android.aar'); + const targetAar = path.join(output, 'android.aar'); + + report.info( + `Copying AAR from ${chalk.blue(path.relative(root, sourceAar))} to ${chalk.blue(path.relative(root, targetAar))}` + ); + await fs.copyFile(sourceAar, targetAar); + + const gradleFile = path.join(output, 'build.gradle'); + report.info( + `Creating AAR Gradle file at ${chalk.blue(path.relative(root, gradleFile))}` + ); + await createGradleFile(gradleFile); + + if (targetOptions.reverseJetify) { + const supportOutputPath = path.join(output, 'support'); + report.info( + `Creating new support output directory at ${chalk.blue(path.relative(root, supportOutputPath))}` + ); + await fs.mkdir(supportOutputPath); + + const supportAar = path.join(supportOutputPath, 'android.aar'); + report.info( + `Using Jetifier to convert AAR from AndroidX to Support AAR at ${chalk.blue(path.relative(root, supportAar))}` + ); + + await jetifier({ + root, + report, + input: targetAar, + output: supportAar, + reverse: true + }); + + const supportGradleFile = path.join(supportOutputPath, 'build.gradle'); + report.info( + `Creating Support AAR Gradle file at ${chalk.blue(path.relative(root, supportGradleFile))}` + ); + await createGradleFile(supportGradleFile); + } + + report.success( + `Wrote files to ${chalk.blue(path.relative(root, output))}` + ); +} diff --git a/src/types.ts b/src/types.ts index 6d762b7b8..5a6f927ac 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,18 +1,19 @@ export type Log = (message: string) => void; +export type Report = { + info: Log; + warn: Log; + success: Log; + error: Log; +}; export type Input = { root: string; source: string; output: string; - report: { - info: Log; - warn: Log; - success: Log; - error: Log; - }; + report: Report; }; -export type Target = 'commonjs' | 'module' | 'typescript'; +export type Target = 'aar' | 'commonjs' | 'module' | 'typescript'; export type Options = { source?: string; diff --git a/src/utils/androidAssemble.ts b/src/utils/androidAssemble.ts new file mode 100644 index 000000000..810f20629 --- /dev/null +++ b/src/utils/androidAssemble.ts @@ -0,0 +1,35 @@ +import path from 'path'; +import chalk from 'chalk'; +import fs from 'fs-extra'; +import { execFileSync } from 'child_process'; +import { platform } from 'os'; +import { Report } from '../types'; + +type Options = { + root: string; + androidPath: string; + report: Report; +}; + +export default async function androidAssemble({ root, androidPath, report }: Options) { + const cwd = path.relative(root, androidPath) + + report.info( + `Assembling Android project in ${chalk.blue(cwd)} with ${chalk.blue('gradle')}` + ); + + const gradleWrapper = './gradlew' + ((platform() === "win32") ? './gradlew.bat' : ''); + if (await fs.pathExists(path.join(androidPath, gradleWrapper))) { + execFileSync(gradleWrapper, ['assemble'], { cwd: androidPath }); + } else { + throw new Error( + `The ${chalk.blue( + 'gradlew' + )} script doesn't seem to present in ${chalk.blue( + androidPath + )}. Make sure you have added it by running ${chalk.blue( + 'gradle wrapper' + )} in that directory.` + ); + } +} diff --git a/src/utils/jetifier.ts b/src/utils/jetifier.ts new file mode 100644 index 000000000..68d271c79 --- /dev/null +++ b/src/utils/jetifier.ts @@ -0,0 +1,36 @@ +import path from 'path'; +import chalk from 'chalk'; +import { execFileSync } from 'child_process'; +import fs from 'fs-extra'; +import { Report } from '../types'; + +type Options = { + root: string; + input: string; + output: string; + reverse: boolean; + report: Report; +}; + +export default async function jetifier({ root, input, output, reverse }: Options) { + const jetifierStandalone = path.join(root, 'node_modules', '.bin', 'jetifier-standalone') + + if (await fs.pathExists(jetifierStandalone)) { + const args = ['-i', input, '-o', output]; + if (reverse) { + args.push("-r"); + } + + execFileSync(jetifierStandalone, args); + } else { + throw new Error( + `The ${chalk.blue( + 'jetifier' + )} binary doesn't seem to be installed under ${chalk.blue( + 'node_modules' + )}. Make sure you have added ${chalk.blue( + 'jetifier' + )} to your ${chalk.blue('devDependencies')}.` + ); + } +} diff --git a/yarn.lock b/yarn.lock index 942062a4f..40f606eb6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3092,6 +3092,11 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= +jetifier@^1.0.0-beta04.2: + version "1.0.0-beta04.2" + resolved "https://registry.yarnpkg.com/jetifier/-/jetifier-1.0.0-beta04.2.tgz#c1bc0adb0fa1a334bb9dd114dcd813c26ac68325" + integrity sha512-4dlUoWJRq3k0A0SaQ11Drh3D/eYcwBt+3N0QeTcE9t2Xe8GkQMrh19/GPzNjE21Q5gDNZFqBcIvjAzB73iwuhw== + js-levenshtein@^1.1.3: version "1.1.6" resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"