am 8aad9a92: Merge change 24897 into eclair
Merge commit '8aad9a92ff05b12d3ab930de29a47f29ab6ea9b0' into eclair-plus-aosp
* commit '8aad9a92ff05b12d3ab930de29a47f29ab6ea9b0':
First cut at a tool to perform automated syncs from Harmony to Dalvik.
diff --git a/tools/integrate/Android.mk b/tools/integrate/Android.mk
new file mode 100644
index 0000000..629a5fd
--- /dev/null
+++ b/tools/integrate/Android.mk
@@ -0,0 +1,19 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ Command.java \
+ Filesystem.java \
+ Git.java \
+ Module.java \
+ Modules.java \
+ MappedDirectory.java \
+ PullHarmonyCode.java \
+ Svn.java
+
+LOCAL_MODULE:= integrate
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(call all-subdir-makefiles)
diff --git a/tools/integrate/Command.java b/tools/integrate/Command.java
new file mode 100644
index 0000000..5e7796f
--- /dev/null
+++ b/tools/integrate/Command.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * An out of process executable.
+ */
+class Command {
+
+ private final List<String> args;
+ private final boolean permitNonZeroExitStatus;
+
+ Command(String... args) {
+ this(Arrays.asList(args));
+ }
+
+ Command(List<String> args) {
+ this.args = new ArrayList<String>(args);
+ this.permitNonZeroExitStatus = false;
+ }
+
+ private Command(Builder builder) {
+ this.args = new ArrayList<String>(builder.args);
+ this.permitNonZeroExitStatus = builder.permitNonZeroExitStatus;
+ }
+
+ static class Builder {
+ private final List<String> args = new ArrayList<String>();
+ private boolean permitNonZeroExitStatus = false;
+
+ public Builder args(String... args) {
+ return args(Arrays.asList(args));
+ }
+
+ public Builder args(Collection<String> args) {
+ this.args.addAll(args);
+ return this;
+ }
+
+ public Builder permitNonZeroExitStatus() {
+ permitNonZeroExitStatus = true;
+ return this;
+ }
+
+ public Command build() {
+ return new Command(this);
+ }
+
+ public List<String> execute() {
+ return build().execute();
+ }
+ }
+
+ public List<String> execute() {
+ try {
+ Process process = new ProcessBuilder()
+ .command(args)
+ .redirectErrorStream(true)
+ .start();
+
+ BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
+ List<String> outputLines = new ArrayList<String>();
+ String outputLine;
+ while ((outputLine = in.readLine()) != null) {
+ outputLines.add(outputLine);
+ }
+
+ if (process.waitFor() != 0 && !permitNonZeroExitStatus) {
+ StringBuilder message = new StringBuilder();
+ for (String line : outputLines) {
+ message.append("\n").append(line);
+ }
+ throw new RuntimeException("Process failed: " + args + message);
+ }
+
+ return outputLines;
+ } catch (IOException e) {
+ throw new RuntimeException("Process failed: " + args, e);
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Process failed: " + args, e);
+ }
+ }
+
+}
diff --git a/tools/integrate/Filesystem.java b/tools/integrate/Filesystem.java
new file mode 100644
index 0000000..a9c1789
--- /dev/null
+++ b/tools/integrate/Filesystem.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Factory for filesystem commands.
+ */
+class Filesystem {
+
+ public void move(String source, String target) {
+ new Command("mv", source, target).execute();
+ }
+
+ /**
+ * Moves all of the files in {@code source} to {@code target}, one at a
+ * time. Unlike {@code move}, this approach works even if the target
+ * directory is nonempty.
+ */
+ public int moveContents(String source, String target) {
+ List<String> files = new Command("find", source, "-type", "f") .execute();
+ for (String file : files) {
+ String targetFile = target + "/" + file.substring(source.length());
+ mkdir(parent(targetFile));
+ new Command("mv", "-i", file, targetFile).execute();
+ }
+ return files.size();
+ }
+
+ private String parent(String file) {
+ return file.substring(0, file.lastIndexOf('/'));
+ }
+
+ public void mkdir(String dir) {
+ new Command("mkdir", "-p", dir).execute();
+ }
+
+ public List<String> find(String where, String name) {
+ return new Command("find", where, "-name", name).execute();
+ }
+
+ public void rm(Collection<String> files) {
+ new Command.Builder().args("rm", "-r").args(files).execute();
+ }
+
+ public void rm(String file) {
+ new Command("rm", "-r", file).execute();
+ }
+}
diff --git a/tools/integrate/Git.java b/tools/integrate/Git.java
new file mode 100644
index 0000000..da7dcfa
--- /dev/null
+++ b/tools/integrate/Git.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Factory for git commands.
+ */
+class Git {
+
+ private static final Pattern STATUS_DELETED
+ = Pattern.compile("#\\tdeleted: (.*)");
+
+ public void branch(String newBranch) {
+ branch(newBranch, "HEAD");
+ }
+
+ /**
+ * @param base another branch, or a revision identifier like {@code HEAD}.
+ */
+ public void branch(String newBranch, String base) {
+ // -b is used by git to branch from another checkout
+ new Command("git", "checkout", "-b", newBranch, base).execute();
+ }
+
+ public void commit(String message) {
+ new Command("git", "commit", "-m", message).execute();
+ }
+
+ public void add(String path) {
+ new Command("git", "add", path).execute();
+ }
+
+ public void remove(Collection<String> paths) {
+ new Command.Builder().args("git", "rm").args(paths).execute();
+ }
+
+ public List<String> merge(String otherBranch) {
+ return new Command.Builder()
+ .args("git", "merge", "-s", "recursive", otherBranch)
+ .permitNonZeroExitStatus()
+ .execute();
+ }
+
+ /**
+ * Returns the files that have been deleted from the filesystem, but that
+ * don't exist in the active git change.
+ */
+ public List<String> listDeleted() {
+ List<String> statusLines = new Command.Builder()
+ .args("git", "status")
+ .permitNonZeroExitStatus()
+ .execute();
+
+ List<String> deletedFiles = new ArrayList<String>();
+ Matcher matcher = STATUS_DELETED.matcher("");
+ for (String line : statusLines) {
+ matcher.reset(line);
+ if (matcher.matches()) {
+ deletedFiles.add(matcher.group(1));
+ }
+ }
+ return deletedFiles;
+ }
+
+ public void rm(List<String> files) {
+ new Command.Builder()
+ .args("git", "rm").args(files)
+ .permitNonZeroExitStatus()
+ .build()
+ .execute();
+ }
+}
diff --git a/tools/integrate/MappedDirectory.java b/tools/integrate/MappedDirectory.java
new file mode 100644
index 0000000..8e28d29
--- /dev/null
+++ b/tools/integrate/MappedDirectory.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * A logical directory that has a different location in Harmony and Dalvik.
+ */
+class MappedDirectory {
+
+ private final String svnPath;
+ private final String gitPath;
+
+ public MappedDirectory(String svnPath, String gitPath) {
+ this.svnPath = svnPath;
+ this.gitPath = gitPath;
+ }
+
+ public String svnPath() {
+ return svnPath;
+ }
+
+ public String gitPath() {
+ return gitPath;
+ }
+
+ @Override public String toString() {
+ return "svn:" + svnPath + " -> git:" + gitPath;
+ }
+}
diff --git a/tools/integrate/Module.java b/tools/integrate/Module.java
new file mode 100644
index 0000000..5cb7035
--- /dev/null
+++ b/tools/integrate/Module.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * A logical unit of code shared between Apache Harmony and Dalvik.
+ */
+class Module {
+
+ private final String svnBaseUrl;
+ private final String path;
+ private final Set<MappedDirectory> mappedDirectories;
+
+ public String getSvnBaseUrl() {
+ return svnBaseUrl;
+ }
+
+ public String path() {
+ return path;
+ }
+
+ public Set<MappedDirectory> getMappedDirectories() {
+ return mappedDirectories;
+ }
+
+ private Module(Builder builder) {
+ this.svnBaseUrl = builder.svnBaseUrl;
+ this.path = builder.path;
+ this.mappedDirectories = new LinkedHashSet<MappedDirectory>(builder.mappedDirectories);
+ }
+
+ public static class Builder {
+ private final String svnBaseUrl;
+ private final String path;
+ private final Set<MappedDirectory> mappedDirectories
+ = new LinkedHashSet<MappedDirectory>();
+
+ public Builder(String svnBaseUrl, String path) {
+ this.svnBaseUrl = svnBaseUrl;
+ this.path = path;
+ }
+
+ public Builder mapDirectory(String svnPath, String gitPath) {
+ mappedDirectories.add(new MappedDirectory(svnPath, gitPath));
+ return this;
+ }
+
+ public Module build() {
+ return new Module(this);
+ }
+ }
+}
diff --git a/tools/integrate/Modules.java b/tools/integrate/Modules.java
new file mode 100644
index 0000000..2475852
--- /dev/null
+++ b/tools/integrate/Modules.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Constants that define modules shared by Harmony and Dalvik.
+ */
+public class Modules {
+
+ private static final String SVN_ROOT
+ = "http://svn.apache.org/repos/asf/harmony/enhanced/classlib/trunk/modules";
+
+ public static final Module ARCHIVE = new Module.Builder(SVN_ROOT, "archive")
+ .mapDirectory("archive/src/main/native/archive/shared",
+ "archive/src/main/native")
+ .mapDirectory("archive/src/main/native/zip/shared",
+ "archive/src/main/native")
+ .build();
+
+ public static final Module CRYPTO = new Module.Builder(SVN_ROOT, "crypto")
+ .mapDirectory("crypto/src/test/api/java.injected/javax",
+ "crypto/src/test/java/org/apache/harmony/crypto/tests/javax")
+ .mapDirectory("crypto/src/test/api/java",
+ "crypto/src/test/java")
+ .mapDirectory("crypto/src/test/resources/serialization",
+ "crypto/src/test/java/serialization")
+ .mapDirectory("crypto/src/test/support/common/java",
+ "crypto/src/test/java")
+ .build();
+
+ public static final Module REGEX
+ = new Module.Builder(SVN_ROOT, "regex").build();
+
+ public static final Module SECURITY = new Module.Builder(SVN_ROOT, "security")
+ .mapDirectory("security/src/main/java/common",
+ "security/src/main/java")
+ .mapDirectory("security/src/main/java/unix/org",
+ "security/src/main/java/org")
+ .mapDirectory("security/src/test/api/java",
+ "security/src/test/java")
+ .build();
+
+ public static final Module TEXT
+ = new Module.Builder(SVN_ROOT, "text").build();
+
+ public static final Module X_NET
+ = new Module.Builder(SVN_ROOT, "x-net").build();
+
+ // TODO: add the other modules
+}
diff --git a/tools/integrate/PullHarmonyCode.java b/tools/integrate/PullHarmonyCode.java
new file mode 100644
index 0000000..6710801
--- /dev/null
+++ b/tools/integrate/PullHarmonyCode.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Download two versions of Apache Harmony from their SVN version, and use it
+ * to perform a three-way merge with Dalvik.
+ */
+public class PullHarmonyCode {
+
+ private final int currentVersion;
+ private final int targetVersion;
+
+ public PullHarmonyCode(int currentVersion, int targetVersion) {
+ this.currentVersion = currentVersion;
+ this.targetVersion = targetVersion;
+ }
+
+ public void pull(Module module) {
+ String path = module.path();
+ String svnOldBranch = path + "_" + currentVersion;
+ String svnNewBranch = path + "_" + targetVersion;
+ String dalvikBranch = path + "_dalvik";
+
+ Git git = new Git();
+ Filesystem filesystem = new Filesystem();
+ Svn svn = new Svn();
+
+ // Assume we're starting with the current Dalvik code. Tuck this away
+ // somewhere while we rewrite history.
+ String temp = "/tmp/" + UUID.randomUUID();
+ filesystem.mkdir(temp);
+
+ // To prepare a three-way-merge, we need a common starting point: the
+ // time at which Dalvik and Harmony were most the same. We'll use the
+ // previous Harmony SVN code as this starting point. We grab the old
+ // code from their repository, and commit it as a git branch.
+ System.out.print("Creating branch " + svnOldBranch + "...");
+ git.branch(svnOldBranch);
+ filesystem.move(path, temp + "/" + path);
+ svn.checkOut(currentVersion, module.getSvnBaseUrl() + "/" + path);
+ filesystem.rm(filesystem.find(path, ".svn"));
+ for (MappedDirectory mappedDirectory : module.getMappedDirectories()) {
+ filesystem.moveContents(mappedDirectory.svnPath(), mappedDirectory.gitPath());
+ }
+ git.rm(git.listDeleted());
+ git.add(path);
+ git.commit(svnOldBranch);
+ System.out.println("done");
+
+ // Create a branch that's derived from the starting point. It will
+ // contain all of the changes Harmony has made from then until now.
+ System.out.print("Creating branch " + svnNewBranch + "...");
+ git.branch(svnNewBranch, svnOldBranch);
+ filesystem.rm(path);
+ svn.checkOut(targetVersion, module.getSvnBaseUrl() + "/" + path);
+ filesystem.rm(filesystem.find(path, ".svn"));
+ for (MappedDirectory mappedDirectory : module.getMappedDirectories()) {
+ filesystem.moveContents(mappedDirectory.svnPath(), mappedDirectory.gitPath());
+ }
+ git.rm(git.listDeleted());
+ git.add(path);
+ git.commit(svnNewBranch);
+ System.out.println("done");
+
+ // Create another branch that's derived from the starting point. It will
+ // contain all of the changes Dalvik has made from then until now.
+ System.out.print("Creating branch " + dalvikBranch + "...");
+ git.branch(dalvikBranch, svnOldBranch);
+ filesystem.rm(path);
+ filesystem.move(temp + "/" + path, path);
+ git.rm(git.listDeleted());
+ git.add(path);
+ git.commit(dalvikBranch);
+ System.out.println("done");
+
+ // Merge the two sets of changes together: Harmony's and Dalvik's. By
+ // initializing a common starting point, git can make better decisions
+ // when the two new versions differ. For example, if today's Dalvik has
+ // a method that today's Harmony does not, it may be because Dalvik
+ // added it, or because Harmony deleted it!
+ System.out.println("Merging " + svnNewBranch + " into " + dalvikBranch + ":");
+ List<String> mergeResults = git.merge(svnNewBranch);
+ for (String mergeResult : mergeResults) {
+ System.out.print(" ");
+ System.out.println(mergeResult);
+ }
+ }
+
+ public static void main(String[] args) {
+// new PullHarmonyCode(527399, 802921).pull(Modules.CRYPTO);
+ new PullHarmonyCode(772995, 802921).pull(Modules.ARCHIVE);
+ }
+}
diff --git a/tools/integrate/Svn.java b/tools/integrate/Svn.java
new file mode 100644
index 0000000..dc9be35
--- /dev/null
+++ b/tools/integrate/Svn.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Factory for Subversion commands.
+ */
+class Svn {
+
+ public void checkOut(int version, String url) {
+ new Command("svn", "co", "-r", "" + version, url).execute();
+ }
+}