mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-02-10 22:45:24 +08:00
Release React Native for Android
This is an early release and there are several things that are known not to work if you're porting your iOS app to Android. See the Known Issues guide on the website. We will work with the community to reach platform parity with iOS.
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
package com.facebook.react;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Base class for tasks that build JS packages. Handles requesting a bundle from the packager server
|
||||
* and putting it into the appropriate folder.
|
||||
*/
|
||||
public abstract class AbstractPackageJsTask extends DefaultTask {
|
||||
|
||||
/**
|
||||
* Describes the status of the JS packager server.
|
||||
*
|
||||
* @see #getPackagerStatus
|
||||
*/
|
||||
private enum PackagerStatus {
|
||||
/**
|
||||
* Packager is up and running
|
||||
*/
|
||||
RUNNING,
|
||||
|
||||
/**
|
||||
* Packager is not running, but could be started
|
||||
*/
|
||||
NOT_RUNNING,
|
||||
|
||||
/**
|
||||
* Packager is not running and can't be started, something else is listening on the configured
|
||||
* port
|
||||
*/
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
private final boolean mDebug;
|
||||
private final ReactGradleExtension mConfig;
|
||||
private final PackagerParams mPackagerParams;
|
||||
|
||||
private final Logger mLogger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
public AbstractPackageJsTask(boolean debug) throws IOException {
|
||||
mDebug = debug;
|
||||
|
||||
mConfig = ReactGradleExtension.getConfig(getProject());
|
||||
mPackagerParams = PackagerParams.getPackagerParams(mConfig, mDebug);
|
||||
|
||||
if (mPackagerParams.isSkip()) {
|
||||
setEnabled(false);
|
||||
} else {
|
||||
setupInputs(mConfig);
|
||||
setupOutputs(mConfig);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a bundle from packager and copy it to the appropriate folder.
|
||||
*/
|
||||
protected void copyBundle() throws Exception {
|
||||
File bundle = getOutputFile(mConfig);
|
||||
bundle.getParentFile().mkdirs();
|
||||
|
||||
PackagerStatus packagerStatus = getPackagerStatus(mConfig);
|
||||
if (packagerStatus == PackagerStatus.NOT_RUNNING) {
|
||||
boolean started = startPackager();
|
||||
if (started) {
|
||||
packagerStatus = getPackagerStatus(mConfig);
|
||||
}
|
||||
if (!started || packagerStatus != PackagerStatus.RUNNING) {
|
||||
throw new Exception(
|
||||
"Could not start packager server. Please start it manually and try again.");
|
||||
}
|
||||
}
|
||||
if (packagerStatus == PackagerStatus.RUNNING) {
|
||||
URL packageUrl = getPackageUrl(mConfig, mPackagerParams);
|
||||
|
||||
InputStream packageStream = packageUrl.openStream();
|
||||
OutputStream bundleStream = new FileOutputStream(bundle);
|
||||
IOUtils.copy(packageStream, bundleStream);
|
||||
IOUtils.closeQuietly(packageStream);
|
||||
IOUtils.closeQuietly(bundleStream);
|
||||
} else if (packagerStatus == PackagerStatus.UNKNOWN) {
|
||||
throw new Exception(
|
||||
"Did not recognize the server at " + mConfig.getPackagerHost() +
|
||||
". Please stop the service listening at this address and try again.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if there is an HTTP server running at the configured address and if it is our packager.
|
||||
* See {@link PackagerStatus} for the possible return values and their meaning.
|
||||
*
|
||||
* @param config the project config that contains packager address information
|
||||
*/
|
||||
private PackagerStatus getPackagerStatus(ReactGradleExtension config) throws URISyntaxException {
|
||||
try {
|
||||
URL statusUrl = new URI("http", config.getPackagerHost(), "/status", null, null).toURL();
|
||||
HttpURLConnection conn = (HttpURLConnection) statusUrl.openConnection();
|
||||
if (conn.getResponseCode() != 200) {
|
||||
// something else must be running on this port
|
||||
return PackagerStatus.UNKNOWN;
|
||||
}
|
||||
InputStream is = conn.getInputStream();
|
||||
String status = IOUtils.toString(is);
|
||||
IOUtils.closeQuietly(is);
|
||||
return status.contains("packager-status:running")
|
||||
? PackagerStatus.RUNNING
|
||||
: PackagerStatus.UNKNOWN;
|
||||
} catch (IOException e) {
|
||||
// connect must have failed
|
||||
return PackagerStatus.NOT_RUNNING;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to spawn a process to run the packager server. Currently support OSX and Linux by running
|
||||
* {@code open launchPackager.command} and {@code xterm -e bash launchPackager.command}
|
||||
* respectively. Always waits 5 seconds for the server to finish initializing.
|
||||
*
|
||||
* @return {@code true} if the server process was started successfully, {@code false} otherwise.
|
||||
*/
|
||||
private boolean startPackager() throws IOException, InterruptedException {
|
||||
if (SystemUtils.IS_OS_MAC_OSX || SystemUtils.IS_OS_LINUX) {
|
||||
String launchPackagerScript =
|
||||
Paths.get(getProject().getProjectDir().getAbsolutePath(), mConfig.getPackagerCommand())
|
||||
.normalize().toString();
|
||||
if (SystemUtils.IS_OS_MAC_OSX) {
|
||||
Runtime.getRuntime().exec(new String[]{"open", launchPackagerScript}, null, null);
|
||||
} else if (SystemUtils.IS_OS_LINUX) {
|
||||
Runtime.getRuntime()
|
||||
.exec(new String[]{"xterm", "-e", "bash", launchPackagerScript}, null, null);
|
||||
}
|
||||
// wait for server to be ready
|
||||
Thread.sleep(5000);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a packager URL for a specific configuration.
|
||||
*
|
||||
* @param config the top-level config of the plugin
|
||||
* @param params packager params to include in the URL
|
||||
*/
|
||||
private URL getPackageUrl(ReactGradleExtension config, PackagerParams params)
|
||||
throws URISyntaxException, MalformedURLException {
|
||||
String query = "dev=" + params.isDev() + "&" +
|
||||
"inlineSourceMap=" + params.isInlineSourceMap() + "&" +
|
||||
"minify=" + params.isMinify() + "&" +
|
||||
"runModule=" + params.isRunModule();
|
||||
return new URI(
|
||||
"http",
|
||||
config.getPackagerHost(),
|
||||
config.getBundlePath(),
|
||||
query,
|
||||
null).toURL();
|
||||
}
|
||||
|
||||
private void setupInputs(ReactGradleExtension config) throws IOException {
|
||||
final long startTime = System.currentTimeMillis();
|
||||
InputsScanner scanner = new InputsScanner();
|
||||
Files.walkFileTree(
|
||||
Paths.get(getProject().getProjectDir().getAbsolutePath(), config.getJsRoot()).normalize(),
|
||||
scanner);
|
||||
final long endTime = System.currentTimeMillis();
|
||||
mLogger.info("Added {} .js files in {}ms", scanner.getCount(), endTime - startTime);
|
||||
}
|
||||
|
||||
private void setupOutputs(ReactGradleExtension config) {
|
||||
getOutputs().file(getOutputFile(config));
|
||||
}
|
||||
|
||||
private File getOutputFile(ReactGradleExtension config) {
|
||||
File assets = new File(
|
||||
getProject().getProjectDir(),
|
||||
FilenameUtils
|
||||
.separatorsToSystem("build/intermediates/assets/" + (mDebug ? "debug" : "release")));
|
||||
return new File(assets, config.getBundleFileName());
|
||||
}
|
||||
|
||||
private class InputsScanner extends SimpleFileVisitor<Path> {
|
||||
|
||||
private final PathMatcher mMatcher = FileSystems.getDefault().getPathMatcher("glob:*.js");
|
||||
private int mCount = 0;
|
||||
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(
|
||||
Path dir, BasicFileAttributes attrs) throws IOException {
|
||||
if ("build".equals(dir.getFileName().toString())) {
|
||||
return FileVisitResult.SKIP_SUBTREE;
|
||||
} else {
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
if (mMatcher.matches(file.getFileName())) {
|
||||
getInputs().file(file.toString());
|
||||
mCount++;
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return mCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.facebook.react;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
|
||||
/**
|
||||
* Gradle task that copies the dev bundle to the debug build's assets.
|
||||
*/
|
||||
public class PackageDebugJsTask extends AbstractPackageJsTask {
|
||||
|
||||
public PackageDebugJsTask() throws IOException {
|
||||
super(true);
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
public void packageJS() throws Exception {
|
||||
copyBundle();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.facebook.react;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
|
||||
/**
|
||||
* Gradle task that copies the prod bundle to the debug build's assets.
|
||||
*/
|
||||
public class PackageReleaseJsTask extends AbstractPackageJsTask {
|
||||
|
||||
public PackageReleaseJsTask() throws IOException {
|
||||
super(false);
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
public void packageJS() throws Exception {
|
||||
copyBundle();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.facebook.react;
|
||||
|
||||
/**
|
||||
* POJO for packager parameters.
|
||||
*/
|
||||
public class PackagerParams {
|
||||
private boolean dev = true;
|
||||
private boolean inlineSourceMap = false;
|
||||
private boolean minify = false;
|
||||
private boolean runModule = true;
|
||||
private boolean skip = false;
|
||||
|
||||
/**
|
||||
* Returns default parameters for debug builds.
|
||||
*/
|
||||
public static PackagerParams devDefaults() {
|
||||
PackagerParams params = new PackagerParams();
|
||||
params.dev = true;
|
||||
params.inlineSourceMap = false;
|
||||
params.minify = false;
|
||||
params.runModule = true;
|
||||
params.skip = true;
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns default parameters for release builds.
|
||||
*/
|
||||
public static PackagerParams releaseDefaults() {
|
||||
PackagerParams params = new PackagerParams();
|
||||
params.dev = false;
|
||||
params.inlineSourceMap = false;
|
||||
params.minify = true;
|
||||
params.runModule = true;
|
||||
params.skip = false;
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract packager parameters from a configuration, or return default values.
|
||||
*
|
||||
* @param config the configuration to extract from
|
||||
* @param debug whether default values should be for debug or prod
|
||||
*/
|
||||
public static PackagerParams getPackagerParams(ReactGradleExtension config, boolean debug) {
|
||||
if (debug) {
|
||||
return config.getDevParams() != null
|
||||
? config.getDevParams()
|
||||
: PackagerParams.devDefaults();
|
||||
} else {
|
||||
return config.getReleaseParams() != null
|
||||
? config.getReleaseParams()
|
||||
: PackagerParams.releaseDefaults();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDev() {
|
||||
return dev;
|
||||
}
|
||||
|
||||
public void dev(boolean dev) {
|
||||
this.dev = dev;
|
||||
}
|
||||
|
||||
public boolean isInlineSourceMap() {
|
||||
return inlineSourceMap;
|
||||
}
|
||||
|
||||
public void inlineSourceMap(boolean inlineSourceMap) {
|
||||
this.inlineSourceMap = inlineSourceMap;
|
||||
}
|
||||
|
||||
public boolean isMinify() {
|
||||
return minify;
|
||||
}
|
||||
|
||||
public void minify(boolean minify) {
|
||||
this.minify = minify;
|
||||
}
|
||||
|
||||
public boolean isRunModule() {
|
||||
return runModule;
|
||||
}
|
||||
|
||||
public void runModule(boolean runModule) {
|
||||
this.runModule = runModule;
|
||||
}
|
||||
|
||||
public boolean isSkip() {
|
||||
return skip;
|
||||
}
|
||||
|
||||
public void skip(boolean skip) {
|
||||
this.skip = skip;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.facebook.react;
|
||||
|
||||
import groovy.lang.Closure;
|
||||
import org.gradle.api.Project;
|
||||
|
||||
/**
|
||||
* POJO-ish class for configuring the plugin.
|
||||
*/
|
||||
public class ReactGradleExtension {
|
||||
private String bundleFileName = "index.android.bundle";
|
||||
private String bundlePath = "/index.android.bundle";
|
||||
private String jsRoot = "../../";
|
||||
private String packagerHost = "localhost:8082";
|
||||
private String packagerCommand =
|
||||
"../../node_modules/react-native/packager/launchAndroidPackager.command";
|
||||
|
||||
private PackagerParams devParams;
|
||||
private PackagerParams releaseParams;
|
||||
|
||||
private Project project;
|
||||
|
||||
public ReactGradleExtension(Project project) {
|
||||
this.project = project;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configuration for a project, or a blank config.
|
||||
*/
|
||||
public static ReactGradleExtension getConfig(Project project) {
|
||||
ReactGradleExtension config =
|
||||
project.getExtensions().findByType(ReactGradleExtension.class);
|
||||
if (config == null) {
|
||||
config = new ReactGradleExtension(project);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
public String getBundleFileName() {
|
||||
return bundleFileName;
|
||||
}
|
||||
|
||||
public void setBundleFileName(String bundleFileName) {
|
||||
this.bundleFileName = bundleFileName;
|
||||
}
|
||||
|
||||
public String getBundlePath() {
|
||||
return bundlePath;
|
||||
}
|
||||
|
||||
public void setBundlePath(String bundlePath) {
|
||||
this.bundlePath = bundlePath;
|
||||
}
|
||||
|
||||
public String getJsRoot() {
|
||||
return jsRoot;
|
||||
}
|
||||
|
||||
public void setJsRoot(String jsRoot) {
|
||||
this.jsRoot = jsRoot;
|
||||
}
|
||||
|
||||
public void setPackagerCommand(String packagerCommand) {
|
||||
this.packagerCommand = packagerCommand;
|
||||
}
|
||||
|
||||
public String getPackagerCommand() {
|
||||
return packagerCommand;
|
||||
}
|
||||
|
||||
public String getPackagerHost() {
|
||||
return packagerHost;
|
||||
}
|
||||
|
||||
public void setPackagerHost(String packagerHost) {
|
||||
this.packagerHost = packagerHost;
|
||||
}
|
||||
|
||||
public PackagerParams getDevParams() {
|
||||
return devParams;
|
||||
}
|
||||
|
||||
public void devParams(Closure closure) {
|
||||
devParams = new PackagerParams();
|
||||
project.configure(devParams, closure);
|
||||
}
|
||||
|
||||
public PackagerParams getReleaseParams() {
|
||||
return releaseParams;
|
||||
}
|
||||
|
||||
public void releaseParams(Closure closure) {
|
||||
releaseParams = new PackagerParams();
|
||||
project.configure(releaseParams, closure);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.facebook.react;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
|
||||
/**
|
||||
* Main entry point for our plugin. When applied to a project, this registers the {@code react}
|
||||
* gradle extension used for configuration and the {@code packageDebugJS} and
|
||||
* {@code packageReleaseJS} tasks. These are set up to run after {@code mergeDebugAssets} and
|
||||
* {@code mergeReleaseAssets} and before {@code processDebugResources} and
|
||||
* {@code processReleaseResources} respectively. If any of these tasks are not found the plugin will
|
||||
* crash (UnknownTaskException), as it was probably applied to a non-standard Android project, or it
|
||||
* was applied incorrectly.
|
||||
*/
|
||||
public class ReactGradlePlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getExtensions().create("react", ReactGradleExtension.class, project);
|
||||
|
||||
project.afterEvaluate(
|
||||
new Action<Project>() {
|
||||
@Override
|
||||
public void execute(Project project) {
|
||||
PackageDebugJsTask packageDebugJsTask =
|
||||
project.getTasks().create("packageDebugJS", PackageDebugJsTask.class);
|
||||
PackageReleaseJsTask packageReleaseJsTask =
|
||||
project.getTasks().create("packageReleaseJS", PackageReleaseJsTask.class);
|
||||
|
||||
packageDebugJsTask.dependsOn("mergeDebugAssets");
|
||||
project.getTasks().getByName("processDebugResources").dependsOn(packageDebugJsTask);
|
||||
|
||||
packageReleaseJsTask.dependsOn("mergeReleaseAssets");
|
||||
project.getTasks().getByName("processReleaseResources").dependsOn(packageReleaseJsTask);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user