Implement Android App Bundles (#2237)
This commit is contained in:
parent
50af1648c9
commit
6516283d20
|
@ -683,4 +683,10 @@ public interface Images extends Resources {
|
|||
*/
|
||||
@Source("com/google/appinventor/images/YRLogo.png")
|
||||
ImageResource YRLogo();
|
||||
|
||||
/**
|
||||
* Download app icon
|
||||
*/
|
||||
@Source("com/google/appinventor/images/get-app.png")
|
||||
ImageResource GetApp();
|
||||
}
|
||||
|
|
|
@ -2270,7 +2270,7 @@ public class Ode implements EntryPoint {
|
|||
* @return nonce
|
||||
*/
|
||||
public String generateNonce() {
|
||||
int v = random.nextInt(1000000);
|
||||
int v = random.nextInt(10000000);
|
||||
nonce = Integer.toString(v, 36); // Base 36 string
|
||||
return nonce;
|
||||
}
|
||||
|
|
|
@ -625,21 +625,21 @@ public interface OdeMessages extends Messages, AutogeneratedOdeMessages {
|
|||
@Description("Label of the button leading to build related cascade items")
|
||||
String buildTabName();
|
||||
|
||||
@DefaultMessage("App ( provide QR code for .apk )")
|
||||
@Description("Label of item for building a project and show barcode")
|
||||
String showBarcodeMenuItem();
|
||||
@DefaultMessage("Android App (.apk)")
|
||||
@Description("Label of item for building a project as apk and showing the qr+download dialog")
|
||||
String showExportAndroidApk();
|
||||
|
||||
@DefaultMessage("App for Google Play ( provide QR code for .apk )")
|
||||
@Description("Label of item for building a project and show barcode")
|
||||
String showBarcodeMenuItem2();
|
||||
@DefaultMessage("[2] Android App (.apk)")
|
||||
@Description("Label of item for building a project as apk and showing the qr+download dialog")
|
||||
String showExportAndroidApk2();
|
||||
|
||||
@DefaultMessage("App ( save .apk to my computer )")
|
||||
@Description("Label of item for building a project and downloading")
|
||||
String downloadToComputerMenuItem();
|
||||
@DefaultMessage("Android App Bundle (.aab)")
|
||||
@Description("Label of item for building a project as aab and showing the qr+download dialog")
|
||||
String showExportAndroidAab();
|
||||
|
||||
@DefaultMessage("App for Google Play ( save .apk to my computer )")
|
||||
@Description("Label of item for building a project and downloading")
|
||||
String downloadToComputerMenuItem2();
|
||||
@DefaultMessage("[2] Android App Bundle (.aab)")
|
||||
@Description("Label of item for building a project as aab and showing the qr+download dialog")
|
||||
String showExportAndroidAab2();
|
||||
|
||||
@DefaultMessage("Generate YAIL")
|
||||
@Description("Label of the cascade item for generating YAIL for a project")
|
||||
|
@ -1497,15 +1497,33 @@ public interface OdeMessages extends Messages, AutogeneratedOdeMessages {
|
|||
|
||||
// Used in explorer/commands/ShowBarcodeCommand.java
|
||||
|
||||
@DefaultMessage("Barcode link for {0}")
|
||||
@Description("Title of barcode dialog.")
|
||||
String barcodeTitle(String projectName);
|
||||
@DefaultMessage("Android App for {0}")
|
||||
@Description("Title of download apk dialog.")
|
||||
String downloadApkDialogTitle(String projectName);
|
||||
|
||||
@DefaultMessage("Android App Bundle for {0} (to be used in Google Play Store)")
|
||||
@Description("Title of download aab dialog.")
|
||||
String downloadAabDialogTitle(String projectName);
|
||||
|
||||
@DefaultMessage("Download .apk now")
|
||||
@Description("Download button shown in barcode dialog")
|
||||
String barcodeDownloadApk();
|
||||
|
||||
@DefaultMessage("Download .aab now")
|
||||
@Description("Download button shown in barcode dialog")
|
||||
String barcodeDownloadAab();
|
||||
|
||||
@DefaultMessage("Note: this barcode is only valid for 2 hours. See {0} the FAQ {1} for info " +
|
||||
"on how to share your app with others.")
|
||||
@Description("Warning in barcode dialog.")
|
||||
String barcodeWarning(String aTagStart, String aTagEnd);
|
||||
|
||||
@DefaultMessage("<b>Click the button to download the app, right-click on it to copy a download link, or scan the " +
|
||||
"code with a barcode scanner to install.</b><br>" +
|
||||
"Note: this link and barcode are only valid for 2 hours. See {0} the FAQ {1} for info on how to share your " +
|
||||
"app with others.")
|
||||
@Description("Warning in barcode dialog.")
|
||||
String barcodeWarning2(String aTagStart, String aTagEnd);
|
||||
|
||||
// Used in explorer/project/Project.java
|
||||
|
||||
@DefaultMessage("Server error: could not load project. Please try again later!")
|
||||
|
|
|
@ -76,10 +76,10 @@ public class TopToolbar extends Composite {
|
|||
private static final String WIDGET_NAME_CHECKPOINT = "Checkpoint";
|
||||
private static final String WIDGET_NAME_MY_PROJECTS = "MyProjects";
|
||||
private static final String WIDGET_NAME_BUILD = "Build";
|
||||
private static final String WIDGET_NAME_BUILD_BARCODE = "Barcode";
|
||||
private static final String WIDGET_NAME_BUILD_DOWNLOAD = "Download";
|
||||
private static final String WIDGET_NAME_BUILD_BARCODE2 = "Barcode2";
|
||||
private static final String WIDGET_NAME_BUILD_DOWNLOAD2 = "Download2";
|
||||
private static final String WIDGET_NAME_BUILD_ANDROID_APK = "BuildApk";
|
||||
private static final String WIDGET_NAME_BUILD_ANDROID_AAB = "BuildAab";
|
||||
private static final String WIDGET_NAME_BUILD_ANDROID_APK2 = "BuildApk2";
|
||||
private static final String WIDGET_NAME_BUILD_ANDROID_AAB2 = "BuildAab2";
|
||||
private static final String WIDGET_NAME_BUILD_YAIL = "Yail";
|
||||
private static final String WIDGET_NAME_CONNECT_TO = "ConnectTo";
|
||||
private static final String WIDGET_NAME_WIRELESS_BUTTON = "Wireless";
|
||||
|
@ -293,10 +293,10 @@ public class TopToolbar extends Composite {
|
|||
|
||||
private void createBuildMenu() {
|
||||
List<DropDownItem> buildItems = Lists.newArrayList();
|
||||
buildItems.add(new DropDownItem(WIDGET_NAME_BUILD_BARCODE, MESSAGES.showBarcodeMenuItem(),
|
||||
new BarcodeAction(false)));
|
||||
buildItems.add(new DropDownItem(WIDGET_NAME_BUILD_DOWNLOAD, MESSAGES.downloadToComputerMenuItem(),
|
||||
new DownloadAction(false)));
|
||||
buildItems.add(new DropDownItem(WIDGET_NAME_BUILD_ANDROID_APK, MESSAGES.showExportAndroidApk(),
|
||||
new BarcodeAction(false, false)));
|
||||
buildItems.add(new DropDownItem(WIDGET_NAME_BUILD_ANDROID_AAB, MESSAGES.showExportAndroidAab(),
|
||||
new BarcodeAction(false, true)));
|
||||
|
||||
// Second Buildserver Menu Items
|
||||
//
|
||||
|
@ -314,10 +314,10 @@ public class TopToolbar extends Composite {
|
|||
|
||||
if (Ode.getInstance().hasSecondBuildserver()) {
|
||||
buildItems.add(null);
|
||||
buildItems.add(new DropDownItem(WIDGET_NAME_BUILD_BARCODE2, MESSAGES.showBarcodeMenuItem2(),
|
||||
new BarcodeAction(true)));
|
||||
buildItems.add(new DropDownItem(WIDGET_NAME_BUILD_DOWNLOAD2, MESSAGES.downloadToComputerMenuItem2(),
|
||||
new DownloadAction(true)));
|
||||
buildItems.add(new DropDownItem(WIDGET_NAME_BUILD_ANDROID_APK2, MESSAGES.showExportAndroidApk2(),
|
||||
new BarcodeAction(true, false)));
|
||||
buildItems.add(new DropDownItem(WIDGET_NAME_BUILD_ANDROID_AAB2, MESSAGES.showExportAndroidAab2(),
|
||||
new BarcodeAction(true, true)));
|
||||
}
|
||||
|
||||
if (AppInventorFeatures.hasYailGenerationOption() && Ode.getInstance().getUser().getIsAdmin()) {
|
||||
|
@ -538,9 +538,11 @@ public class TopToolbar extends Composite {
|
|||
private class BarcodeAction implements Command {
|
||||
|
||||
private boolean secondBuildserver = false;
|
||||
private boolean isAab;
|
||||
|
||||
public BarcodeAction(boolean secondBuildserver) {
|
||||
public BarcodeAction(boolean secondBuildserver, boolean isAab) {
|
||||
this.secondBuildserver = secondBuildserver;
|
||||
this.isAab = isAab;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -550,10 +552,10 @@ public class TopToolbar extends Composite {
|
|||
String target = YoungAndroidProjectNode.YOUNG_ANDROID_TARGET_ANDROID;
|
||||
ChainableCommand cmd = new SaveAllEditorsCommand(
|
||||
new GenerateYailCommand(
|
||||
new BuildCommand(target, secondBuildserver,
|
||||
new BuildCommand(target, secondBuildserver, isAab,
|
||||
new ShowProgressBarCommand(target,
|
||||
new WaitForBuildResultCommand(target,
|
||||
new ShowBarcodeCommand(target)), "BarcodeAction"))));
|
||||
new ShowBarcodeCommand(target, isAab)), "BarcodeAction"))));
|
||||
if (!Ode.getInstance().getWarnBuild(secondBuildserver)) {
|
||||
cmd = new WarningDialogCommand(target, secondBuildserver, cmd);
|
||||
Ode.getInstance().setWarnBuild(secondBuildserver, true);
|
||||
|
@ -568,38 +570,6 @@ public class TopToolbar extends Composite {
|
|||
}
|
||||
}
|
||||
|
||||
private class DownloadAction implements Command {
|
||||
|
||||
private boolean secondBuildserver = false;
|
||||
|
||||
DownloadAction(boolean secondBuildserver) {
|
||||
this.secondBuildserver = secondBuildserver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
ProjectRootNode projectRootNode = Ode.getInstance().getCurrentYoungAndroidProjectRootNode();
|
||||
if (projectRootNode != null) {
|
||||
String target = YoungAndroidProjectNode.YOUNG_ANDROID_TARGET_ANDROID;
|
||||
ChainableCommand cmd = new SaveAllEditorsCommand(
|
||||
new GenerateYailCommand(
|
||||
new BuildCommand(target, secondBuildserver,
|
||||
new ShowProgressBarCommand(target,
|
||||
new WaitForBuildResultCommand(target,
|
||||
new DownloadProjectOutputCommand(target)), "DownloadAction"))));
|
||||
if (!Ode.getInstance().getWarnBuild(secondBuildserver)) {
|
||||
cmd = new WarningDialogCommand(target, secondBuildserver, cmd);
|
||||
Ode.getInstance().setWarnBuild(secondBuildserver, true);
|
||||
}
|
||||
cmd.startExecuteChain(Tracking.PROJECT_ACTION_BUILD_DOWNLOAD_YA, projectRootNode,
|
||||
new Command() {
|
||||
@Override
|
||||
public void execute() {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
private static class ExportProjectAction implements Command {
|
||||
@Override
|
||||
public void execute() {
|
||||
|
@ -1104,8 +1074,12 @@ public class TopToolbar extends Composite {
|
|||
fileDropDown.setItemEnabled(MESSAGES.saveMenuItem(), false);
|
||||
fileDropDown.setItemEnabled(MESSAGES.saveAsMenuItem(), false);
|
||||
fileDropDown.setItemEnabled(MESSAGES.checkpointMenuItem(), false);
|
||||
buildDropDown.setItemEnabled(MESSAGES.showBarcodeMenuItem(), false);
|
||||
buildDropDown.setItemEnabled(MESSAGES.downloadToComputerMenuItem(), false);
|
||||
buildDropDown.setItemEnabled(MESSAGES.showExportAndroidApk(), false);
|
||||
buildDropDown.setItemEnabled(MESSAGES.showExportAndroidAab(), false);
|
||||
if (Ode.getInstance().hasSecondBuildserver()) {
|
||||
buildDropDown.setItemEnabled(MESSAGES.showExportAndroidApk2(), false);
|
||||
buildDropDown.setItemEnabled(MESSAGES.showExportAndroidAab2(), false);
|
||||
}
|
||||
} else { // We have to be in the Designer/Blocks view
|
||||
fileDropDown.setItemEnabled(MESSAGES.deleteProjectButton(), true);
|
||||
fileDropDown.setItemEnabled(MESSAGES.trashProjectMenuItem(), true);
|
||||
|
@ -1115,8 +1089,12 @@ public class TopToolbar extends Composite {
|
|||
fileDropDown.setItemEnabled(MESSAGES.saveMenuItem(), true);
|
||||
fileDropDown.setItemEnabled(MESSAGES.saveAsMenuItem(), true);
|
||||
fileDropDown.setItemEnabled(MESSAGES.checkpointMenuItem(), true);
|
||||
buildDropDown.setItemEnabled(MESSAGES.showBarcodeMenuItem(), true);
|
||||
buildDropDown.setItemEnabled(MESSAGES.downloadToComputerMenuItem(), true);
|
||||
buildDropDown.setItemEnabled(MESSAGES.showExportAndroidApk(), true);
|
||||
buildDropDown.setItemEnabled(MESSAGES.showExportAndroidAab(), true);
|
||||
if (Ode.getInstance().hasSecondBuildserver()) {
|
||||
buildDropDown.setItemEnabled(MESSAGES.showExportAndroidApk2(), true);
|
||||
buildDropDown.setItemEnabled(MESSAGES.showExportAndroidAab2(), true);
|
||||
}
|
||||
}
|
||||
updateKeystoreFileMenuButtons(true);
|
||||
}
|
||||
|
|
|
@ -31,14 +31,15 @@ public class BuildCommand extends ChainableCommand {
|
|||
|
||||
// Whether or not to use the second buildserver
|
||||
private boolean secondBuildserver = false;
|
||||
private boolean isAab;
|
||||
|
||||
/**
|
||||
* Creates a new build command.
|
||||
*
|
||||
* @param target the build target
|
||||
*/
|
||||
public BuildCommand(String target, boolean secondBuildserver) {
|
||||
this(target, secondBuildserver, null);
|
||||
public BuildCommand(String target, boolean secondBuildserver, boolean isAab) {
|
||||
this(target, secondBuildserver, isAab, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -48,8 +49,9 @@ public class BuildCommand extends ChainableCommand {
|
|||
* @param target the build target
|
||||
* @param nextCommand the command to execute after the build has finished
|
||||
*/
|
||||
public BuildCommand(String target, boolean secondBuildserver, ChainableCommand nextCommand) {
|
||||
public BuildCommand(String target, boolean secondBuildserver, boolean isAab, ChainableCommand nextCommand) {
|
||||
super(nextCommand);
|
||||
this.isAab = isAab;
|
||||
this.target = target;
|
||||
this.secondBuildserver = secondBuildserver;
|
||||
}
|
||||
|
@ -126,6 +128,6 @@ public class BuildCommand extends ChainableCommand {
|
|||
};
|
||||
|
||||
String nonce = ode.generateNonce();
|
||||
ode.getProjectService().build(node.getProjectId(), nonce, target, secondBuildserver, callback);
|
||||
ode.getProjectService().build(node.getProjectId(), nonce, target, secondBuildserver, isAab, callback);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,11 +11,17 @@ import com.google.appinventor.client.editor.youngandroid.BlocklyPanel;
|
|||
import com.google.appinventor.client.output.OdeLog;
|
||||
import com.google.appinventor.shared.rpc.project.ProjectNode;
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.dom.client.Document;
|
||||
import com.google.gwt.dom.client.SpanElement;
|
||||
import com.google.gwt.dom.client.Style;
|
||||
import com.google.gwt.event.dom.client.ClickEvent;
|
||||
import com.google.gwt.event.dom.client.ClickHandler;
|
||||
import com.google.gwt.user.client.Window;
|
||||
import com.google.gwt.user.client.ui.Anchor;
|
||||
import com.google.gwt.user.client.ui.Button;
|
||||
import com.google.gwt.user.client.ui.DialogBox;
|
||||
import com.google.gwt.user.client.ui.HTML;
|
||||
import com.google.gwt.user.client.ui.Image;
|
||||
import com.google.gwt.user.client.ui.HorizontalPanel;
|
||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
|
||||
|
@ -23,8 +29,7 @@ import static com.google.appinventor.client.Ode.MESSAGES;
|
|||
|
||||
/**
|
||||
* Command for displaying a barcode for the target of a project.
|
||||
*
|
||||
* <p/>This command is often chained with SaveAllEditorsCommand and BuildCommand.
|
||||
* This command is often chained with SaveAllEditorsCommand and BuildCommand.
|
||||
*
|
||||
* @author markf@google.com (Mark Friedman)
|
||||
*/
|
||||
|
@ -32,17 +37,19 @@ public class ShowBarcodeCommand extends ChainableCommand {
|
|||
|
||||
// The build target
|
||||
private String target;
|
||||
private boolean isAab;
|
||||
|
||||
/**
|
||||
* Creates a new command for showing a barcode for the target of a project.
|
||||
*
|
||||
* @param target the build target
|
||||
*/
|
||||
public ShowBarcodeCommand(String target) {
|
||||
public ShowBarcodeCommand(String target, boolean isAab) {
|
||||
// Since we don't know when the barcode dialog is finished, we can't
|
||||
// support a command after this one.
|
||||
super(null); // no next command
|
||||
this.target = target;
|
||||
this.isAab = isAab;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -54,55 +61,108 @@ public class ShowBarcodeCommand extends ChainableCommand {
|
|||
public void execute(final ProjectNode node) {
|
||||
// Display a barcode for an url pointing at our server's download servlet
|
||||
String barcodeUrl = GWT.getHostPageBaseURL()
|
||||
+ "b/" + Ode.getInstance().getNonce();
|
||||
+ "b/" + Ode.getInstance().getNonce();
|
||||
OdeLog.log("Barcode url is: " + barcodeUrl);
|
||||
new BarcodeDialogBox(node.getName(), barcodeUrl).center();
|
||||
new BarcodeDialogBox(node.getName(), barcodeUrl, isAab).center();
|
||||
}
|
||||
|
||||
static class BarcodeDialogBox extends DialogBox {
|
||||
|
||||
BarcodeDialogBox(String projectName, String appInstallUrl) {
|
||||
BarcodeDialogBox(String projectName, final String appInstallUrl, boolean isAab) {
|
||||
super(false, true);
|
||||
setStylePrimaryName("ode-DialogBox");
|
||||
setText(MESSAGES.barcodeTitle(projectName));
|
||||
setText(isAab ? MESSAGES.downloadAabDialogTitle(projectName) : MESSAGES.downloadApkDialogTitle(projectName));
|
||||
|
||||
// Main layout panel
|
||||
VerticalPanel contentPanel = new VerticalPanel();
|
||||
|
||||
// Container
|
||||
HorizontalPanel container = new HorizontalPanel();
|
||||
|
||||
// Container > Left
|
||||
VerticalPanel left = new VerticalPanel();
|
||||
|
||||
// Container > Left > Download Button
|
||||
ClickHandler downloadHandler = new ClickHandler() {
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
Window.open(appInstallUrl, "_self", "enabled");
|
||||
}
|
||||
};
|
||||
HorizontalPanel downloadPanel = new HorizontalPanel();
|
||||
downloadPanel.setHorizontalAlignment(HorizontalPanel.ALIGN_CENTER);
|
||||
Anchor downloadButton = new Anchor();
|
||||
downloadButton.setHref(appInstallUrl);
|
||||
downloadButton.addStyleName("gwt-Button");
|
||||
downloadButton.addStyleName("download-button");
|
||||
// Container > Left > Download Button > Image
|
||||
Image downloadIcon = new Image(Ode.getImageBundle().GetApp());
|
||||
downloadIcon.setSize("110px", "100px");
|
||||
downloadIcon.addStyleName("download-icon");
|
||||
downloadButton.getElement().appendChild(downloadIcon.getElement());
|
||||
// Container > Left > Download Button > Inner Text
|
||||
SpanElement text = Document.get().createSpanElement();
|
||||
text.setInnerHTML(isAab ? MESSAGES.barcodeDownloadAab() : MESSAGES.barcodeDownloadApk());
|
||||
downloadButton.getElement().appendChild(text);
|
||||
downloadButton.addClickHandler(downloadHandler);
|
||||
downloadPanel.add(downloadButton);
|
||||
downloadPanel.setSize("100%", "30px");
|
||||
left.add(downloadPanel);
|
||||
|
||||
// Container > Left
|
||||
container.add(left);
|
||||
|
||||
// The Android App Bundle should only be used to publish the app through Google Play Store. Thus,
|
||||
// it does not make sense to provide a QR code which the user might think they can scan and directly
|
||||
// install in the phone directly.
|
||||
if (!isAab) {
|
||||
// Container > Right
|
||||
VerticalPanel right = new VerticalPanel();
|
||||
|
||||
// Container > Right > Barcode
|
||||
HTML barcodeQrcode = new HTML("<center>" + BlocklyPanel.getQRCode(appInstallUrl) + "</center>");
|
||||
barcodeQrcode.addStyleName("download-barcode");
|
||||
right.add(barcodeQrcode);
|
||||
|
||||
// Container > Right
|
||||
container.add(right);
|
||||
}
|
||||
|
||||
// Container
|
||||
contentPanel.add(container);
|
||||
|
||||
// Warning
|
||||
// The warning label is added only in APK files, as there is no QR code for the AAB. It is supposed that
|
||||
// users download the bundle just when they get it.
|
||||
if (!isAab) {
|
||||
HorizontalPanel warningPanel = new HorizontalPanel();
|
||||
warningPanel.setHorizontalAlignment(HorizontalPanel.ALIGN_LEFT);
|
||||
HTML warningLabel = new HTML(MESSAGES.barcodeWarning2(
|
||||
"<a href=\"" + "http://appinventor.mit.edu/explore/ai2/share.html" +
|
||||
"\" target=\"_blank\">",
|
||||
"</a>"));
|
||||
warningLabel.setWordWrap(true);
|
||||
warningLabel.setWidth("400px"); // set width to get the text to wrap
|
||||
warningLabel.getElement().getStyle().setMarginTop(10, Style.Unit.PX);
|
||||
warningPanel.add(warningLabel);
|
||||
contentPanel.add(warningPanel);
|
||||
}
|
||||
|
||||
// OK button
|
||||
ClickHandler buttonHandler = new ClickHandler() {
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
hide();
|
||||
}
|
||||
};
|
||||
|
||||
Button cancelButton = new Button(MESSAGES.cancelButton());
|
||||
cancelButton.addClickHandler(buttonHandler);
|
||||
Button okButton = new Button(MESSAGES.okButton());
|
||||
okButton.addClickHandler(buttonHandler);
|
||||
HTML barcodeQrcode = new HTML("<center>" + BlocklyPanel.getQRCode(appInstallUrl) + "</center>");
|
||||
HTML linkQrcode = new HTML("<center><a href=\"" + appInstallUrl + "\" target=\"_blank\">" + appInstallUrl + "</a></center>");
|
||||
HorizontalPanel buttonPanel = new HorizontalPanel();
|
||||
buttonPanel.setHorizontalAlignment(HorizontalPanel.ALIGN_CENTER);
|
||||
HTML warningLabel = new HTML(MESSAGES.barcodeWarning(
|
||||
"<a href=\"" + "http://appinventor.mit.edu/explore/ai2/share.html" +
|
||||
"\" target=\"_blank\">",
|
||||
"</a>"));
|
||||
warningLabel.setWordWrap(true);
|
||||
warningLabel.setWidth("200px"); // set width to get the text to wrap
|
||||
HorizontalPanel warningPanel = new HorizontalPanel();
|
||||
warningPanel.setHorizontalAlignment(HorizontalPanel.ALIGN_LEFT);
|
||||
warningPanel.add(warningLabel);
|
||||
|
||||
// The cancel button is removed from the panel since it has no meaning in this
|
||||
// context. But the logic is still here in case we want to restore it, and as
|
||||
// an example of how to code this stuff in GWT.
|
||||
// buttonPanel.add(cancelButton);
|
||||
Button okButton = new Button(MESSAGES.dismissButton());
|
||||
okButton.addClickHandler(buttonHandler);
|
||||
buttonPanel.add(okButton);
|
||||
buttonPanel.setSize("100%", "24px");
|
||||
VerticalPanel contentPanel = new VerticalPanel();
|
||||
contentPanel.add(barcodeQrcode);
|
||||
contentPanel.add(linkQrcode);
|
||||
contentPanel.add(buttonPanel);
|
||||
contentPanel.add(warningPanel);
|
||||
// contentPanel.setSize("320px", "100%");
|
||||
|
||||
add(contentPanel);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -225,7 +225,7 @@ function checkValidDrop(e) {
|
|||
top.HTML5DragDrop_confirmOverwriteKey(doUploadKeystore(item));
|
||||
} else if (isExtension(item) && top.HTML5DragDrop_isProjectEditorOpen()) {
|
||||
uploadExtension(item);
|
||||
} else if (goog.string.endsWith(item.name, '.apk')) {
|
||||
} else if (goog.string.endsWith(item.name, '.apk') || goog.string.endsWith(item.name, '.aab')) {
|
||||
top.HTML5DragDrop_reportError(2);
|
||||
} else if (top.HTML5DragDrop_isProjectEditorOpen()) {
|
||||
uploadAsset(item);
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 606 B |
|
@ -48,7 +48,7 @@ public final class FileExporterImpl implements FileExporter {
|
|||
// There should never be more than one .apk file.
|
||||
|
||||
for (String fileName : files) {
|
||||
if (fileName.endsWith(".apk")) {
|
||||
if (fileName.endsWith(".apk") || fileName.endsWith(".aab")) {
|
||||
byte[] content = storageIo.downloadRawFile(userId, projectId, fileName);
|
||||
return new RawFile(StorageUtil.basename(fileName), content);
|
||||
}
|
||||
|
|
|
@ -564,11 +564,11 @@ public class ProjectServiceImpl extends OdeRemoteServiceServlet implements Proje
|
|||
* @return results of build
|
||||
*/
|
||||
@Override
|
||||
public RpcResult build(long projectId, String nonce, String target, boolean secondBuildserver) {
|
||||
public RpcResult build(long projectId, String nonce, String target, boolean secondBuildserver, boolean isAab) {
|
||||
// Dispatch
|
||||
final String userId = userInfoProvider.getUserId();
|
||||
return getProjectRpcImpl(userId, projectId).build(
|
||||
userInfoProvider.getUser(), projectId, nonce, target, secondBuildserver);
|
||||
userInfoProvider.getUser(), projectId, nonce, target, secondBuildserver, isAab);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -335,7 +335,7 @@ public abstract class CommonProjectService {
|
|||
*
|
||||
* @return build results
|
||||
*/
|
||||
public abstract RpcResult build(User user, long projectId, String nonce, String target, boolean secondBuildserver);
|
||||
public abstract RpcResult build(User user, long projectId, String nonce, String target, boolean secondBuildserver, boolean isAab);
|
||||
|
||||
/**
|
||||
* Gets the result of a build command for the project.
|
||||
|
|
|
@ -24,6 +24,7 @@ import com.google.appinventor.server.project.CommonProjectService;
|
|||
import com.google.appinventor.server.project.utils.Security;
|
||||
import com.google.appinventor.server.properties.json.ServerJsonParser;
|
||||
import com.google.appinventor.server.storage.StorageIo;
|
||||
import com.google.appinventor.server.util.UriBuilder;
|
||||
import com.google.appinventor.shared.properties.json.JSONParser;
|
||||
import com.google.appinventor.shared.rpc.RpcResult;
|
||||
import com.google.appinventor.shared.rpc.ServerLayout;
|
||||
|
@ -685,7 +686,7 @@ public final class YoungAndroidProjectService extends CommonProjectService {
|
|||
*/
|
||||
@Override
|
||||
public RpcResult build(User user, long projectId, String nonce, String target,
|
||||
boolean secondBuildserver) {
|
||||
boolean secondBuildserver, boolean isAab) {
|
||||
String userId = user.getUserId();
|
||||
String projectName = storageIo.getProjectName(userId, projectId);
|
||||
String outputFileDir = BUILD_FOLDER + '/' + target;
|
||||
|
@ -708,7 +709,8 @@ public final class YoungAndroidProjectService extends CommonProjectService {
|
|||
userId,
|
||||
projectId,
|
||||
secondBuildserver,
|
||||
outputFileDir));
|
||||
outputFileDir,
|
||||
isAab));
|
||||
HttpURLConnection connection = (HttpURLConnection) buildServerUrl.openConnection();
|
||||
connection.setDoOutput(true);
|
||||
connection.setRequestMethod("POST");
|
||||
|
@ -946,9 +948,9 @@ public final class YoungAndroidProjectService extends CommonProjectService {
|
|||
}
|
||||
|
||||
String buildErrorMsg(String exceptionName, URL buildURL, String userId, long projectId) {
|
||||
return "Request to build failed with " + exceptionName
|
||||
+ ", user=" + userId + ", project=" + projectId
|
||||
+ ", build URL is " + (buildURL != null ? buildURL : "null") + " ["
|
||||
return "Request to build failed with " + exceptionName
|
||||
+ ", user=" + userId + ", project=" + projectId
|
||||
+ ", build URL is " + (buildURL != null ? buildURL : "null") + " ["
|
||||
+ (buildURL != null ? buildURL.toString().length() : "n/a") + "]";
|
||||
}
|
||||
|
||||
|
@ -956,21 +958,22 @@ public final class YoungAndroidProjectService extends CommonProjectService {
|
|||
// a little more complicated when we want to get the URL from an App Engine config file or
|
||||
// command line argument.
|
||||
private String getBuildServerUrlStr(String userName, String userId,
|
||||
long projectId, boolean secondBuildserver, String fileName)
|
||||
throws UnsupportedEncodingException, EncryptionException {
|
||||
return "http://" + (secondBuildserver ? buildServerHost2.get() : buildServerHost.get()) +
|
||||
"/buildserver/build-all-from-zip-async"
|
||||
+ "?uname=" + URLEncoder.encode(userName, "UTF-8")
|
||||
+ (sendGitVersion.get()
|
||||
? "&gitBuildVersion="
|
||||
+ URLEncoder.encode(GitBuildId.getVersion(), "UTF-8")
|
||||
: "")
|
||||
+ "&callback="
|
||||
+ URLEncoder.encode("http://" + getCurrentHost() + ServerLayout.ODE_BASEURL_NOAUTH
|
||||
+ ServerLayout.RECEIVE_BUILD_SERVLET + "/"
|
||||
+ Security.encryptUserAndProjectId(userId, projectId)
|
||||
+ "/" + fileName,
|
||||
"UTF-8");
|
||||
long projectId, boolean secondBuildserver, String fileName, boolean isAab)
|
||||
throws EncryptionException {
|
||||
UriBuilder uriBuilder = new UriBuilder(
|
||||
"http://"
|
||||
+ (secondBuildserver ? buildServerHost2.get() : buildServerHost.get())
|
||||
+ "/buildserver/build-all-from-zip-async")
|
||||
.add("uname", userName)
|
||||
.add("callback", "http://" + getCurrentHost() + ServerLayout.ODE_BASEURL_NOAUTH +
|
||||
ServerLayout.RECEIVE_BUILD_SERVLET + "/" +
|
||||
Security.encryptUserAndProjectId(userId, projectId) + "/" +
|
||||
fileName)
|
||||
.add("ext", isAab ? "aab" : "apk");
|
||||
if (sendGitVersion.get()) {
|
||||
uriBuilder.add("gitBuildVersion", GitBuildId.getVersion());
|
||||
}
|
||||
return uriBuilder.build();
|
||||
}
|
||||
|
||||
private String getCurrentHost() {
|
||||
|
|
|
@ -1510,7 +1510,7 @@ public class ObjectifyStorageIo implements StorageIo {
|
|||
if (!useGcs) // Using legacy blob store solution
|
||||
return false;
|
||||
boolean shouldUse = fileName.contains("assets/")
|
||||
|| fileName.endsWith(".apk");
|
||||
|| fileName.endsWith(".apk") || fileName.endsWith(".aab");
|
||||
if (shouldUse)
|
||||
return true; // Use GCS for package output and assets
|
||||
boolean mayUse = (fileName.contains("src/") && fileName.endsWith(".blk")) // AI1 Blocks Files
|
||||
|
|
|
@ -147,9 +147,9 @@ public interface StorageIo {
|
|||
*
|
||||
* @param userId user ID
|
||||
* @param projectId project ID
|
||||
* @param boolean flag
|
||||
* @param flag
|
||||
*/
|
||||
void setMoveToTrashFlag(final String userId, final long projectId,boolean flag);
|
||||
void setMoveToTrashFlag(final String userId, final long projectId, boolean flag);
|
||||
|
||||
/**
|
||||
* Returns an array with the user's projects.
|
||||
|
|
|
@ -315,7 +315,7 @@ public interface ProjectService extends RemoteService {
|
|||
*
|
||||
* @return results of invoking the build command
|
||||
*/
|
||||
RpcResult build(long projectId, String nonce, String target, boolean secondBuildserver);
|
||||
RpcResult build(long projectId, String nonce, String target, boolean secondBuildserver, boolean isAab);
|
||||
|
||||
/**
|
||||
* Gets the result of a build command for the project from the back-end.
|
||||
|
|
|
@ -16,7 +16,6 @@ import java.util.List;
|
|||
* Interface for the service providing project information. All declarations
|
||||
* in this interface are mirrored in {@link ProjectService}. For further
|
||||
* information see {@link ProjectService}.
|
||||
*
|
||||
*/
|
||||
public interface ProjectServiceAsync {
|
||||
|
||||
|
@ -134,7 +133,7 @@ public interface ProjectServiceAsync {
|
|||
/**
|
||||
* @see ProjectService#loadraw(long, String)
|
||||
*/
|
||||
void loadraw(long projectId, String fileId, AsyncCallback<byte []> callback);
|
||||
void loadraw(long projectId, String fileId, AsyncCallback<byte[]> callback);
|
||||
|
||||
/**
|
||||
* @see ProjectService#loadraw2(long, String)
|
||||
|
@ -171,7 +170,7 @@ public interface ProjectServiceAsync {
|
|||
/**
|
||||
* @see ProjectService#build(long, String, String, boolean)
|
||||
*/
|
||||
void build(long projectId, String nonce, String target, boolean secondBuildserver, AsyncCallback<RpcResult> callback);
|
||||
void build(long projectId, String nonce, String target, boolean secondBuildserver, boolean isAab, AsyncCallback<RpcResult> callback);
|
||||
|
||||
/**
|
||||
* @see ProjectService#getBuildResult(long, String)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<!-- Copyright 2007-2009 Google Inc. All Rights Reserved. -->
|
||||
<!-- Copyright 2011-2016 Massachusetts Institute of Technology. All Rights Reserved. -->
|
||||
<!-- Copyright 2011-2020 Massachusetts Institute of Technology. All Rights Reserved. -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
|
|
@ -2712,3 +2712,30 @@ div.dropdiv p {
|
|||
margin: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.download-button {
|
||||
display: grid;
|
||||
max-width: 130px;
|
||||
margin: 0 !important;
|
||||
margin-top: 12px !important;
|
||||
margin-left: -3px !important;
|
||||
height: 130px;
|
||||
}
|
||||
|
||||
.download-icon {
|
||||
height: 110px;
|
||||
width: 110px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.download-barcode:before {
|
||||
content: "";
|
||||
background-color: #888;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 160px;
|
||||
top: 40px;
|
||||
left: 50%;
|
||||
display: block;
|
||||
}
|
||||
|
|
|
@ -63,6 +63,10 @@
|
|||
<ant inheritAll="false" useNativeBasedir="true" dir="buildserver" target="PlayApp"/>
|
||||
</target>
|
||||
|
||||
<target name="PlayAppAab">
|
||||
<ant inheritAll="false" useNativeBasedir="true" dir="buildserver" target="PlayAppAab"/>
|
||||
</target>
|
||||
|
||||
<target name="PlayAppExtras">
|
||||
<ant inheritAll="false" useNativeBasedir="true" dir="buildserver" target="PlayAppExtras"/>
|
||||
</target>
|
||||
|
|
|
@ -78,6 +78,7 @@
|
|||
<property name="classes.tools.dir" location="${BuildServer-class.dir}/tools" />
|
||||
<copy todir="${classes.tools.dir}">
|
||||
<fileset dir="${lib.dir}/android/tools" includes="*/aapt" />
|
||||
<fileset dir="${lib.dir}/android/tools" includes="*/aapt2" />
|
||||
<fileset dir="${lib.dir}/android/tools" includes="*/libwinpthread-1.dll" />
|
||||
<fileset dir="${lib.dir}/android/tools" includes="*/lib64/*" />
|
||||
</copy>
|
||||
|
@ -174,7 +175,8 @@
|
|||
===================================================================== -->
|
||||
<target name="CheckPlayApp"
|
||||
depends="GenPlayAppSrcZip,BuildServer">
|
||||
<uptodate property="PlayApp.uptodate" targetfile="${public.build.dir}/MIT AI2 Companion.apk">
|
||||
<property name="ext" value="apk"/>
|
||||
<uptodate property="PlayApp.uptodate" targetfile="${public.build.dir}/MIT AI2 Companion.${ext}">
|
||||
<srcfiles file="${local.build.dir}/aiplayapp.zip"/>
|
||||
<srcfiles dir="${run.lib.dir}" includes="*.jar"/>
|
||||
</uptodate>
|
||||
|
@ -186,6 +188,11 @@
|
|||
<target name="PlayApp"
|
||||
depends="CheckPlayApp"
|
||||
unless="PlayApp.uptodate">
|
||||
<condition property="ext" value="apk">
|
||||
<not>
|
||||
<isset property="ext"/>
|
||||
</not>
|
||||
</condition>
|
||||
<java classname="com.google.appinventor.buildserver.Main" fork="true" failonerror="true">
|
||||
<classpath>
|
||||
<fileset dir="${run.lib.dir}" includes="*.jar" />
|
||||
|
@ -203,9 +210,16 @@
|
|||
<arg value="${public.build.dir}" />
|
||||
<arg value="--dexCacheDir" />
|
||||
<arg value="${public.build.dir}/dexCache" />
|
||||
<arg value="--ext" />
|
||||
<arg value="${ext}" />
|
||||
</java>
|
||||
</target>
|
||||
|
||||
<target name="PlayAppAab">
|
||||
<property name="ext" value="aab"/>
|
||||
<antcall target="PlayApp"/>
|
||||
</target>
|
||||
|
||||
<target name="CheckPlayAppExtras">
|
||||
<uptodate property="PlayAppExtras.uptodate" targetfile="${public.build.dir}/MITAI2Companion-full.apk">
|
||||
<srcfiles file="${local.build.dir}/aiplayapp.zip"/>
|
||||
|
|
|
@ -0,0 +1,323 @@
|
|||
// -*- mode: java; c-basic-offset: 2; -*-
|
||||
// Copyright 2009-2011 Google, All Rights reserved
|
||||
// Copyright 2011-2021 MIT, All rights reserved
|
||||
// Released under the Apache License, Version 2.0
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
package com.google.appinventor.buildserver;
|
||||
|
||||
import com.google.appinventor.buildserver.util.AabZipper;
|
||||
import com.google.common.io.Files;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
/**
|
||||
* This Callable class will convert the compiled files into an Android App Bundle.
|
||||
* An AAB file structure looks like this:
|
||||
* - assets.pb
|
||||
* - resources.pb
|
||||
* - native.pb
|
||||
* - manifest/AndroidManifest.xml
|
||||
* - dex/
|
||||
* - res/
|
||||
* - assets/
|
||||
* - lib/
|
||||
*/
|
||||
public class AabCompiler implements Callable<Boolean> {
|
||||
private PrintStream out;
|
||||
private File buildDir;
|
||||
private int mx;
|
||||
|
||||
private AabPaths aab;
|
||||
private String originalDexDir = null;
|
||||
private File originalLibsDir = null;
|
||||
|
||||
private String bundletool = null;
|
||||
private String jarsigner = null;
|
||||
|
||||
private String deploy = null;
|
||||
private String keystore = null;
|
||||
|
||||
private class AabPaths {
|
||||
private File root = null;
|
||||
private File base = null;
|
||||
private File protoApk = null;
|
||||
|
||||
private File assetsDir = null;
|
||||
private File dexDir = null;
|
||||
private File libDir = null;
|
||||
private File manifestDir = null;
|
||||
private File resDir = null;
|
||||
|
||||
public File getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
public void setRoot(File root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
public File getBase() {
|
||||
return base;
|
||||
}
|
||||
|
||||
public void setBase(File base) {
|
||||
this.base = base;
|
||||
}
|
||||
|
||||
public File getProtoApk() {
|
||||
return protoApk;
|
||||
}
|
||||
|
||||
public void setProtoApk(File protoApk) {
|
||||
this.protoApk = protoApk;
|
||||
}
|
||||
|
||||
public File getAssetsDir() {
|
||||
return assetsDir;
|
||||
}
|
||||
|
||||
public void setAssetsDir(File assetsDir) {
|
||||
this.assetsDir = assetsDir;
|
||||
}
|
||||
|
||||
public File getDexDir() {
|
||||
return dexDir;
|
||||
}
|
||||
|
||||
public void setDexDir(File dexDir) {
|
||||
this.dexDir = dexDir;
|
||||
}
|
||||
|
||||
public File getLibDir() {
|
||||
return libDir;
|
||||
}
|
||||
|
||||
public void setLibDir(File libDir) {
|
||||
this.libDir = libDir;
|
||||
}
|
||||
|
||||
public File getManifestDir() {
|
||||
return manifestDir;
|
||||
}
|
||||
|
||||
public void setManifestDir(File manifestDir) {
|
||||
this.manifestDir = manifestDir;
|
||||
}
|
||||
|
||||
public File getResDir() {
|
||||
return resDir;
|
||||
}
|
||||
|
||||
public void setResDir(File resDir) {
|
||||
this.resDir = resDir;
|
||||
}
|
||||
}
|
||||
|
||||
public AabCompiler(PrintStream out, File buildDir, int mx) {
|
||||
assert out != null;
|
||||
assert buildDir != null;
|
||||
assert mx > 0;
|
||||
|
||||
this.out = out;
|
||||
this.buildDir = buildDir;
|
||||
this.mx = mx;
|
||||
|
||||
aab = new AabPaths();
|
||||
}
|
||||
|
||||
public AabCompiler setDexDir(String dexDir) {
|
||||
this.originalDexDir = dexDir;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AabCompiler setLibsDir(File libsDir) {
|
||||
this.originalLibsDir = libsDir;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AabCompiler setBundletool(String bundletool) {
|
||||
this.bundletool = bundletool;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AabCompiler setDeploy(String deploy) {
|
||||
this.deploy = deploy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AabCompiler setKeystore(String keystore) {
|
||||
this.keystore = keystore;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AabCompiler setJarsigner(String jarsigner) {
|
||||
this.jarsigner = jarsigner;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AabCompiler setProtoApk(File apk) {
|
||||
aab.setProtoApk(apk);
|
||||
return this;
|
||||
}
|
||||
|
||||
private static File createDir(File parentDir, String name) {
|
||||
File dir = new File(parentDir, name);
|
||||
if (!dir.exists()) {
|
||||
dir.mkdir();
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean call() {
|
||||
out.println("___________Creating structure");
|
||||
aab.setRoot(createDir(buildDir, "aab"));
|
||||
if (!createStructure()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out.println("___________Extracting protobuf resources");
|
||||
if (!extractProtobuf()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out.println("________Running bundletool");
|
||||
if (!bundletool()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out.println("________Signing bundle");
|
||||
if (!jarsigner()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean createStructure() {
|
||||
// Manifest is extracted from the protobuffed APK
|
||||
aab.setManifestDir(createDir(aab.root, "manifest"));
|
||||
|
||||
// Resources are extracted from the protobuffed APK
|
||||
aab.setResDir(createDir(aab.root, "res"));
|
||||
|
||||
// Assets are extracted from the protobuffed APK
|
||||
aab.setAssetsDir(createDir(aab.root, "assets"));
|
||||
|
||||
aab.setDexDir(createDir(aab.root, "dex"));
|
||||
File[] dexFiles = new File(originalDexDir).listFiles();
|
||||
if (dexFiles != null) {
|
||||
for (File dex : dexFiles) {
|
||||
if (dex.isFile()) {
|
||||
try {
|
||||
Files.move(dex, new File(aab.dexDir, dex.getName()));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
aab.setLibDir(createDir(aab.root, "lib"));
|
||||
File[] libFiles = originalLibsDir.listFiles();
|
||||
if (libFiles != null) {
|
||||
for (File lib : libFiles) {
|
||||
try {
|
||||
Files.move(lib, new File(createDir(aab.root, "lib"), lib.getName()));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean extractProtobuf() {
|
||||
try (ZipInputStream is = new ZipInputStream(new FileInputStream(aab.getProtoApk()))) {
|
||||
ZipEntry entry;
|
||||
byte[] buffer = new byte[1024];
|
||||
while ((entry = is.getNextEntry()) != null) {
|
||||
String n = entry.getName();
|
||||
File f = null;
|
||||
if (n.equals("AndroidManifest.xml")) {
|
||||
f = new File(aab.getManifestDir(), n);
|
||||
} else if (n.equals("resources.pb")) {
|
||||
f = new File(aab.getRoot(), n);
|
||||
} else if (n.startsWith("assets")) {
|
||||
f = new File(aab.getAssetsDir(), n.substring(("assets").length()));
|
||||
} else if (n.startsWith("res")) {
|
||||
f = new File(aab.getResDir(), n.substring(("res").length()));
|
||||
}
|
||||
|
||||
if (f != null) {
|
||||
f.getParentFile().mkdirs();
|
||||
try (FileOutputStream fos = new FileOutputStream(f)) {
|
||||
int len;
|
||||
while ((len = is.read(buffer)) > 0) {
|
||||
fos.write(buffer, 0, len);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean bundletool() {
|
||||
aab.setBase(new File(buildDir, "base.zip"));
|
||||
|
||||
if (!AabZipper.zipBundle(aab.getRoot(), aab.getBase(), aab.getRoot().getName() + File.separator)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<String> bundletoolCommandLine = new ArrayList<String>();
|
||||
bundletoolCommandLine.add(System.getProperty("java.home") + "/bin/java");
|
||||
bundletoolCommandLine.add("-jar");
|
||||
bundletoolCommandLine.add("-mx" + mx + "M");
|
||||
bundletoolCommandLine.add(bundletool);
|
||||
bundletoolCommandLine.add("build-bundle");
|
||||
bundletoolCommandLine.add("--modules=" + aab.getBase());
|
||||
bundletoolCommandLine.add("--output=" + deploy);
|
||||
String[] bundletoolBuildCommandLine = bundletoolCommandLine.toArray(new String[0]);
|
||||
|
||||
return Execution.execute(null, bundletoolBuildCommandLine, System.out, System.err);
|
||||
}
|
||||
|
||||
private boolean jarsigner() {
|
||||
List<String> jarsignerCommandLine = new ArrayList<String>();
|
||||
jarsignerCommandLine.add(jarsigner);
|
||||
jarsignerCommandLine.add("-sigalg");
|
||||
jarsignerCommandLine.add("SHA256withRSA");
|
||||
jarsignerCommandLine.add("-digestalg");
|
||||
jarsignerCommandLine.add("SHA-256");
|
||||
jarsignerCommandLine.add("-keystore");
|
||||
jarsignerCommandLine.add(keystore);
|
||||
jarsignerCommandLine.add("-storepass");
|
||||
jarsignerCommandLine.add("android");
|
||||
jarsignerCommandLine.add(deploy);
|
||||
jarsignerCommandLine.add("AndroidKey");
|
||||
String[] jarsignerSignCommandLine = jarsignerCommandLine.toArray(new String[0]);
|
||||
|
||||
return Execution.execute(null, jarsignerSignCommandLine, System.out, System.err);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
// -*- mode: java; c-basic-offset: 2; -*-
|
||||
// Copyright 2009-2011 Google, All Rights reserved
|
||||
// Copyright 2011-2012 MIT, All rights reserved
|
||||
// Copyright 2011-2021 MIT, All rights reserved
|
||||
// Released under the Apache License, Version 2.0
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
package com.google.appinventor.buildserver;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// -*- mode: java; c-basic-offset: 2; -*-
|
||||
// Copyright 2009-2011 Google, All Rights reserved
|
||||
// Copyright 2011-2012 MIT, All rights reserved
|
||||
// Copyright 2011-2021 MIT, All rights reserved
|
||||
// Released under the Apache License, Version 2.0
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
package com.google.appinventor.buildserver;
|
||||
|
@ -147,6 +147,7 @@ public class BuildServer {
|
|||
@Option(name = "--debug",
|
||||
usage = "Turn on debugging, which enables the non-async calls of the buildserver.")
|
||||
boolean debug = false;
|
||||
|
||||
@Option(name = "--dexCacheDir",
|
||||
usage = "the directory to cache the pre-dexed libraries")
|
||||
String dexCacheDir = null;
|
||||
|
@ -400,7 +401,7 @@ public class BuildServer {
|
|||
@POST
|
||||
@Path("build-from-zip")
|
||||
@Produces("application/vnd.android.package-archive;charset=utf-8")
|
||||
public Response buildFromZipFile(@QueryParam("uname") String userName, File zipFile)
|
||||
public Response buildFromZipFile(@QueryParam("uname") String userName, @QueryParam("ext") String ext, File zipFile)
|
||||
throws IOException {
|
||||
// Set the inputZip field so we can delete the input zip file later in cleanUp.
|
||||
inputZip = zipFile;
|
||||
|
@ -410,8 +411,10 @@ public class BuildServer {
|
|||
return Response.status(Response.Status.FORBIDDEN).type(MediaType.TEXT_PLAIN_TYPE)
|
||||
.entity("Entry point unavailable unless debugging.").build();
|
||||
|
||||
boolean isAab = Main.AAB_EXTENSION_VALUE.equals(ext);
|
||||
|
||||
try {
|
||||
build(userName, zipFile, null);
|
||||
build(userName, zipFile, isAab, null);
|
||||
String attachedFilename = outputApk.getName();
|
||||
FileInputStream outputApkDeleteOnClose = new DeleteFileOnCloseFileInputStream(outputApk);
|
||||
// Set the outputApk field to null so that it won't be deleted in cleanUp().
|
||||
|
@ -443,7 +446,7 @@ public class BuildServer {
|
|||
@POST
|
||||
@Path("build-all-from-zip")
|
||||
@Produces("application/zip;charset=utf-8")
|
||||
public Response buildAllFromZipFile(@QueryParam("uname") String userName, File inputZipFile)
|
||||
public Response buildAllFromZipFile(@QueryParam("uname") String userName, @QueryParam("ext") String ext, File inputZipFile)
|
||||
throws IOException, JSONException {
|
||||
// Set the inputZip field so we can delete the input zip file later in cleanUp.
|
||||
inputZip = inputZipFile;
|
||||
|
@ -453,8 +456,10 @@ public class BuildServer {
|
|||
return Response.status(Response.Status.FORBIDDEN).type(MediaType.TEXT_PLAIN_TYPE)
|
||||
.entity("Entry point unavailable unless debugging.").build();
|
||||
|
||||
boolean isAab = Main.AAB_EXTENSION_VALUE.equals(ext);
|
||||
|
||||
try {
|
||||
buildAndCreateZip(userName, inputZipFile, null);
|
||||
buildAndCreateZip(userName, inputZipFile, isAab, null);
|
||||
String attachedFilename = outputZip.getName();
|
||||
FileInputStream outputZipDeleteOnClose = new DeleteFileOnCloseFileInputStream(outputZip);
|
||||
// Set the outputZip field to null so that it won't be deleted in cleanUp().
|
||||
|
@ -501,6 +506,7 @@ public class BuildServer {
|
|||
@QueryParam("uname") final String userName,
|
||||
@QueryParam("callback") final String callbackUrlStr,
|
||||
@QueryParam("gitBuildVersion") final String gitBuildVersion,
|
||||
@QueryParam("ext") final String ext,
|
||||
final File inputZipFile) throws IOException {
|
||||
// Set the inputZip field so we can delete the input zip file later in
|
||||
// cleanUp.
|
||||
|
@ -508,6 +514,8 @@ public class BuildServer {
|
|||
inputZip.deleteOnExit(); // In case build server is killed before cleanUp executes.
|
||||
String requesting_host = (new URL(callbackUrlStr)).getHost();
|
||||
|
||||
final boolean isAab = Main.AAB_EXTENSION_VALUE.equals(ext);
|
||||
|
||||
//for the request for update part, the file should be empty
|
||||
if (inputZip.length() == 0L) {
|
||||
cleanUp();
|
||||
|
@ -562,7 +570,7 @@ public class BuildServer {
|
|||
try {
|
||||
LOG.info("START NEW BUILD " + count);
|
||||
checkMemory();
|
||||
buildAndCreateZip(userName, inputZipFile, new ProgressReporter(callbackUrlStr));
|
||||
buildAndCreateZip(userName, inputZipFile, isAab, new ProgressReporter(callbackUrlStr));
|
||||
// Send zip back to the callbackUrl
|
||||
LOG.info("CallbackURL: " + callbackUrlStr);
|
||||
URL callbackUrl = new URL(callbackUrlStr);
|
||||
|
@ -621,12 +629,12 @@ public class BuildServer {
|
|||
// are now handled via a callback mechanism. The "50" here is just a plug
|
||||
// number.
|
||||
return Response.ok().type(MediaType.TEXT_PLAIN_TYPE)
|
||||
.entity("" + 50).build();
|
||||
.entity("" + 0).build();
|
||||
}
|
||||
|
||||
private void buildAndCreateZip(String userName, File inputZipFile, ProgressReporter reporter)
|
||||
private void buildAndCreateZip(String userName, File inputZipFile, boolean isAab, ProgressReporter reporter)
|
||||
throws IOException, JSONException {
|
||||
Result buildResult = build(userName, inputZipFile, reporter);
|
||||
Result buildResult = build(userName, inputZipFile, isAab, reporter);
|
||||
boolean buildSucceeded = buildResult.succeeded();
|
||||
outputZip = File.createTempFile(inputZipFile.getName(), ".zip");
|
||||
outputZip.deleteOnExit(); // In case build server is killed before cleanUp executes.
|
||||
|
@ -664,7 +672,7 @@ public class BuildServer {
|
|||
return buildOutputJsonObj.toString();
|
||||
}
|
||||
|
||||
private Result build(String userName, File zipFile, ProgressReporter reporter) throws IOException {
|
||||
private Result build(String userName, File zipFile, boolean isAab, ProgressReporter reporter) throws IOException {
|
||||
outputDir = Files.createTempDir();
|
||||
// We call outputDir.deleteOnExit() here, in case build server is killed before cleanUp
|
||||
// executes. However, it is likely that the directory won't be empty and therefore, won't
|
||||
|
@ -673,7 +681,7 @@ public class BuildServer {
|
|||
outputDir.deleteOnExit();
|
||||
Result buildResult = projectBuilder.build(userName, new ZipFile(zipFile), outputDir, null,
|
||||
false, false, false, null,
|
||||
commandLineOptions.childProcessRamMb, commandLineOptions.dexCacheDir, reporter);
|
||||
commandLineOptions.childProcessRamMb, commandLineOptions.dexCacheDir, reporter, isAab);
|
||||
String buildOutput = buildResult.getOutput();
|
||||
LOG.info("Build output: " + buildOutput);
|
||||
String buildError = buildResult.getError();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// -*- mode: java; c-basic-offset: 2; -*-
|
||||
// Copyright 2009-2011 Google, All Rights reserved
|
||||
// Copyright 2011-2020 MIT, All rights reserved
|
||||
// Copyright 2011-2021 MIT, All rights reserved
|
||||
// Released under the Apache License, Version 2.0
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
|
@ -55,11 +55,15 @@ import java.util.Set;
|
|||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.codehaus.jettison.json.JSONArray;
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
|
@ -193,6 +197,15 @@ public final class Compiler {
|
|||
private static final String WINDOWS_ZIPALIGN_TOOL =
|
||||
"/tools/windows/zipalign";
|
||||
|
||||
private static final String LINUX_AAPT2_TOOL =
|
||||
"/tools/linux/aapt2";
|
||||
private static final String MAC_AAPT2_TOOL =
|
||||
"/tools/mac/aapt2";
|
||||
private static final String WINDOWS_AAPT2_TOOL =
|
||||
"/tools/windows/aapt2";
|
||||
private static final String BUNDLETOOL_JAR =
|
||||
RUNTIME_FILES_DIR + "bundletool.jar";
|
||||
|
||||
@VisibleForTesting
|
||||
static final String YAIL_RUNTIME = RUNTIME_FILES_DIR + "runtime.scm";
|
||||
|
||||
|
@ -255,6 +268,11 @@ public final class Compiler {
|
|||
*/
|
||||
private File mergedResDir;
|
||||
|
||||
/**
|
||||
* Zip file containing all compiled resources with AAPT2
|
||||
*/
|
||||
private File resourcesZip;
|
||||
|
||||
// TODO(Will): Remove the following Set once the deprecated
|
||||
// @SimpleBroadcastReceiver annotation is removed. It should
|
||||
// should remain for the time being because otherwise we'll break
|
||||
|
@ -897,7 +915,6 @@ public final class Compiler {
|
|||
writeDialogTheme(out, "AIAlertDialog", "Theme.AppCompat.Dialog.Alert");
|
||||
}
|
||||
}
|
||||
|
||||
out.write("<style name=\"TextAppearance.AppCompat.Button\">\n");
|
||||
out.write("<item name=\"textAllCaps\">false</item>\n");
|
||||
out.write("</style>\n");
|
||||
|
@ -1333,7 +1350,7 @@ public final class Compiler {
|
|||
boolean isForCompanion, boolean isForEmulator,
|
||||
boolean includeDangerousPermissions, String keystoreFilePath,
|
||||
int childProcessRam, String dexCacheDir, String outputFileName,
|
||||
BuildServer.ProgressReporter reporter) throws IOException, JSONException {
|
||||
BuildServer.ProgressReporter reporter, boolean isAab) throws IOException, JSONException {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
// Create a new compiler instance for the compilation
|
||||
|
@ -1341,6 +1358,11 @@ public final class Compiler {
|
|||
isForCompanion, isForEmulator, includeDangerousPermissions, childProcessRam, dexCacheDir,
|
||||
reporter);
|
||||
|
||||
// Set initial progress to 0%
|
||||
if (reporter != null) {
|
||||
reporter.report(0);
|
||||
}
|
||||
|
||||
compiler.generateAssets();
|
||||
compiler.generateActivities();
|
||||
compiler.generateMetadata();
|
||||
|
@ -1473,11 +1495,20 @@ public final class Compiler {
|
|||
out.println("________Invoking AAPT");
|
||||
File deployDir = createDir(buildDir, "deploy");
|
||||
String tmpPackageName = deployDir.getAbsolutePath() + SLASH +
|
||||
project.getProjectName() + ".ap_";
|
||||
project.getProjectName() + "." + (isAab ? "apk" : "ap_");
|
||||
File srcJavaDir = createDir(buildDir, "generated/src");
|
||||
File rJavaDir = createDir(buildDir, "generated/symbols");
|
||||
if (!compiler.runAaptPackage(manifestFile, resDir, tmpPackageName, srcJavaDir, rJavaDir)) {
|
||||
return false;
|
||||
if (isAab) {
|
||||
if (!compiler.runAapt2Compile(resDir)) {
|
||||
return false;
|
||||
}
|
||||
if (!compiler.runAapt2Link(manifestFile, tmpPackageName, rJavaDir)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!compiler.runAaptPackage(manifestFile, resDir, tmpPackageName, srcJavaDir, rJavaDir)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (reporter != null) {
|
||||
reporter.report(30);
|
||||
|
@ -1486,6 +1517,8 @@ public final class Compiler {
|
|||
// Create class files.
|
||||
out.println("________Compiling source files");
|
||||
File classesDir = createDir(buildDir, "classes");
|
||||
File tmpDir = createDir(buildDir, "tmp");
|
||||
String dexedClassesDir = tmpDir.getAbsolutePath();
|
||||
if (!compiler.generateRClasses(classesDir)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1513,8 +1546,6 @@ public final class Compiler {
|
|||
// method of identifying via a hash of the path won't work when files
|
||||
// are copied into temporary storage) and processed via a hacked up version of
|
||||
// Android SDK's Dex Ant task
|
||||
File tmpDir = createDir(buildDir, "tmp");
|
||||
String dexedClassesDir = tmpDir.getAbsolutePath();
|
||||
if (!compiler.runMultidex(classesDir, dexedClassesDir)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1522,30 +1553,36 @@ public final class Compiler {
|
|||
reporter.report(85);
|
||||
}
|
||||
|
||||
// Seal the apk with ApkBuilder
|
||||
out.println("________Invoking ApkBuilder");
|
||||
String fileName = outputFileName;
|
||||
if (fileName == null) {
|
||||
fileName = project.getProjectName() + ".apk";
|
||||
}
|
||||
String apkAbsolutePath = deployDir.getAbsolutePath() + SLASH + fileName;
|
||||
if (!compiler.runApkBuilder(apkAbsolutePath, tmpPackageName, dexedClassesDir)) {
|
||||
return false;
|
||||
}
|
||||
if (reporter != null) {
|
||||
reporter.report(95);
|
||||
}
|
||||
if (isAab) {
|
||||
if (!compiler.bundleTool(buildDir, childProcessRam, tmpPackageName, outputFileName, deployDir, keystoreFilePath, dexedClassesDir)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Seal the apk with ApkBuilder
|
||||
out.println("________Invoking ApkBuilder");
|
||||
String fileName = outputFileName;
|
||||
if (fileName == null) {
|
||||
fileName = project.getProjectName() + ".apk";
|
||||
}
|
||||
String apkAbsolutePath = deployDir.getAbsolutePath() + SLASH + fileName;
|
||||
if (!compiler.runApkBuilder(apkAbsolutePath, tmpPackageName, dexedClassesDir)) {
|
||||
return false;
|
||||
}
|
||||
if (reporter != null) {
|
||||
reporter.report(95);
|
||||
}
|
||||
|
||||
// ZipAlign the apk file
|
||||
out.println("________ZipAligning the apk file");
|
||||
if (!compiler.runZipAlign(apkAbsolutePath, tmpDir)) {
|
||||
return false;
|
||||
}
|
||||
// ZipAlign the apk file
|
||||
out.println("________ZipAligning the apk file");
|
||||
if (!compiler.runZipAlign(apkAbsolutePath, tmpDir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sign the apk file
|
||||
out.println("________Signing the apk file");
|
||||
if (!compiler.runApkSigner(apkAbsolutePath, keystoreFilePath)) {
|
||||
return false;
|
||||
// Sign the apk file
|
||||
out.println("________Signing the apk file");
|
||||
if (!compiler.runApkSigner(apkAbsolutePath, keystoreFilePath)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (reporter != null) {
|
||||
|
@ -2282,7 +2319,7 @@ public final class Compiler {
|
|||
aaptPackageCommandLineArgs.add("--output-text-symbols");
|
||||
aaptPackageCommandLineArgs.add(symbolOutputDir.getAbsolutePath());
|
||||
aaptPackageCommandLineArgs.add("--no-version-vectors");
|
||||
appRJava = new File(sourceOutputDir, packageName.replaceAll("\\.", "/") + "/R.java");
|
||||
appRJava = new File(sourceOutputDir, packageName.replaceAll("\\.", SLASH) + SLASH + "R.java");
|
||||
appRTxt = new File(symbolOutputDir, "R.txt");
|
||||
}
|
||||
String[] aaptPackageCommandLine = aaptPackageCommandLineArgs.toArray(new String[aaptPackageCommandLineArgs.size()]);
|
||||
|
@ -2304,6 +2341,142 @@ public final class Compiler {
|
|||
return true;
|
||||
}
|
||||
|
||||
private boolean runAapt2Compile(File resDir) {
|
||||
resourcesZip = new File(resDir, "resources.zip");
|
||||
String aaptTool;
|
||||
String aapt2Tool;
|
||||
String osName = System.getProperty("os.name");
|
||||
if (osName.equals("Mac OS X")) {
|
||||
aaptTool = MAC_AAPT_TOOL;
|
||||
aapt2Tool = MAC_AAPT2_TOOL;
|
||||
} else if (osName.equals("Linux")) {
|
||||
aaptTool = LINUX_AAPT_TOOL;
|
||||
aapt2Tool = LINUX_AAPT2_TOOL;
|
||||
} else if (osName.startsWith("Windows")) {
|
||||
aaptTool = WINDOWS_AAPT_TOOL;
|
||||
aapt2Tool = WINDOWS_AAPT2_TOOL;
|
||||
} else {
|
||||
LOG.warning("YAIL compiler - cannot run AAPT2 on OS " + osName);
|
||||
err.println("YAIL compiler - cannot run AAPT2 on OS " + osName);
|
||||
userErrors.print(String.format(ERROR_IN_STAGE, "AAPT2"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mergeResources(resDir, project.getBuildDirectory(), aaptTool)) {
|
||||
LOG.warning("Unable to merge resources");
|
||||
err.println("Unable to merge resources");
|
||||
userErrors.print(String.format(ERROR_IN_STAGE, "AAPT"));
|
||||
return false;
|
||||
}
|
||||
|
||||
libSetup(); // Setup /tmp/lib64 on Linux
|
||||
|
||||
List<String> aapt2CommandLine = new ArrayList<>();
|
||||
aapt2CommandLine.add(getResource(aapt2Tool));
|
||||
aapt2CommandLine.add("compile");
|
||||
aapt2CommandLine.add("--dir");
|
||||
aapt2CommandLine.add(mergedResDir.getAbsolutePath());
|
||||
aapt2CommandLine.add("-o");
|
||||
aapt2CommandLine.add(resourcesZip.getAbsolutePath());
|
||||
aapt2CommandLine.add("--no-crunch");
|
||||
aapt2CommandLine.add("-v");
|
||||
String[] aapt2CompileCommandLine = aapt2CommandLine.toArray(new String[0]);
|
||||
|
||||
long startAapt2 = System.currentTimeMillis();
|
||||
if (!Execution.execute(null, aapt2CompileCommandLine, System.out, System.err)) {
|
||||
LOG.warning("YAIL compiler - AAPT2 compile execution failed.");
|
||||
err.println("YAIL compiler - AAPT2 compile execution failed.");
|
||||
userErrors.print(String.format(ERROR_IN_STAGE, "AAPT2 compile"));
|
||||
return false;
|
||||
}
|
||||
|
||||
String aaptTimeMessage = "AAPT2 compile time: " + ((System.currentTimeMillis() - startAapt2) / 1000.0) + " seconds";
|
||||
out.println(aaptTimeMessage);
|
||||
LOG.info(aaptTimeMessage);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean runAapt2Link(File manifestFile, String tmpPackageName, File symbolOutputDir) {
|
||||
String aapt2Tool;
|
||||
String osName = System.getProperty("os.name");
|
||||
if (osName.equals("Mac OS X")) {
|
||||
aapt2Tool = MAC_AAPT2_TOOL;
|
||||
} else if (osName.equals("Linux")) {
|
||||
aapt2Tool = LINUX_AAPT2_TOOL;
|
||||
} else if (osName.startsWith("Windows")) {
|
||||
aapt2Tool = WINDOWS_AAPT2_TOOL;
|
||||
} else {
|
||||
LOG.warning("YAIL compiler - cannot run AAPT2 on OS " + osName);
|
||||
err.println("YAIL compiler - cannot run AAPT2 on OS " + osName);
|
||||
userErrors.print(String.format(ERROR_IN_STAGE, "AAPT2"));
|
||||
return false;
|
||||
}
|
||||
appRTxt = new File(symbolOutputDir, "R.txt");
|
||||
|
||||
List<String> aapt2CommandLine = new ArrayList<>();
|
||||
aapt2CommandLine.add(getResource(aapt2Tool));
|
||||
aapt2CommandLine.add("link");
|
||||
aapt2CommandLine.add("--proto-format");
|
||||
aapt2CommandLine.add("-o");
|
||||
aapt2CommandLine.add(tmpPackageName);
|
||||
aapt2CommandLine.add("-I");
|
||||
aapt2CommandLine.add(getResource(ANDROID_RUNTIME));
|
||||
aapt2CommandLine.add("-R");
|
||||
aapt2CommandLine.add(resourcesZip.getAbsolutePath());
|
||||
aapt2CommandLine.add("-A");
|
||||
aapt2CommandLine.add(createDir(project.getBuildDirectory(), ASSET_DIR_NAME).getAbsolutePath());
|
||||
aapt2CommandLine.add("--manifest");
|
||||
aapt2CommandLine.add(manifestFile.getAbsolutePath());
|
||||
aapt2CommandLine.add("--output-text-symbols");
|
||||
aapt2CommandLine.add(appRTxt.getAbsolutePath());
|
||||
aapt2CommandLine.add("--auto-add-overlay");
|
||||
aapt2CommandLine.add("--no-version-vectors");
|
||||
aapt2CommandLine.add("--no-auto-version");
|
||||
aapt2CommandLine.add("--no-version-transitions");
|
||||
aapt2CommandLine.add("--no-resource-deduping");
|
||||
aapt2CommandLine.add("-v");
|
||||
String[] aapt2LinkCommandLine = aapt2CommandLine.toArray(new String[0]);
|
||||
|
||||
long startAapt2 = System.currentTimeMillis();
|
||||
if (!Execution.execute(null, aapt2LinkCommandLine, System.out, System.err)) {
|
||||
LOG.warning("YAIL compiler - AAPT2 link execution failed.");
|
||||
err.println("YAIL compiler - AAPT2 link execution failed.");
|
||||
userErrors.print(String.format(ERROR_IN_STAGE, "AAPT2 link"));
|
||||
return false;
|
||||
}
|
||||
|
||||
String aaptTimeMessage = "AAPT2 link time: " + ((System.currentTimeMillis() - startAapt2) / 1000.0) + " seconds";
|
||||
out.println(aaptTimeMessage);
|
||||
LOG.info(aaptTimeMessage);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean bundleTool(File buildDir, int childProcessRam, String tmpPackageName,
|
||||
String outputFileName, File deployDir, String keystoreFilePath, String dexedClassesDir) {
|
||||
try {
|
||||
String jarsignerTool = "jarsigner";
|
||||
String fileName = outputFileName;
|
||||
if (fileName == null) {
|
||||
fileName = project.getProjectName() + ".aab";
|
||||
}
|
||||
|
||||
AabCompiler aabCompiler = new AabCompiler(out, buildDir, childProcessRam - 200)
|
||||
.setLibsDir(libsDir)
|
||||
.setProtoApk(new File(tmpPackageName))
|
||||
.setJarsigner(jarsignerTool)
|
||||
.setBundletool(getResource(BUNDLETOOL_JAR))
|
||||
.setDeploy(deployDir.getAbsolutePath() + SLASH + fileName)
|
||||
.setKeystore(keystoreFilePath)
|
||||
.setDexDir(dexedClassesDir);
|
||||
|
||||
Future<Boolean> aab = Executors.newSingleThreadExecutor().submit(aabCompiler);
|
||||
return aab.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean insertNativeLibs(File buildDir){
|
||||
/**
|
||||
* Native libraries are targeted for particular processor architectures.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// -*- mode: java; c-basic-offset: 2; -*-
|
||||
// Copyright 2009-2011 Google, All Rights reserved
|
||||
// Copyright 2011-2012 MIT, All rights reserved
|
||||
// Copyright 2011-2021 MIT, All rights reserved
|
||||
// Released under the Apache License, Version 2.0
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// -*- mode: java; c-basic-offset: 2; -*-
|
||||
// Copyright 2009-2011 Google, All Rights reserved
|
||||
// Copyright 2011-2019 MIT, All rights reserved
|
||||
// Copyright 2011-2021 MIT, All rights reserved
|
||||
// Released under the Apache License, Version 2.0
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// -*- mode: java; c-basic-offset: 2; -*-
|
||||
// Copyright 2009-2011 Google, All Rights reserved
|
||||
// Copyright 2011-2012 MIT, All rights reserved
|
||||
// Copyright 2011-2021 MIT, All rights reserved
|
||||
// Released under the Apache License, Version 2.0
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
|
@ -23,28 +23,31 @@ import java.util.zip.ZipFile;
|
|||
*/
|
||||
public final class Main {
|
||||
|
||||
public final static String APK_EXTENSION_VALUE = "apk";
|
||||
public final static String AAB_EXTENSION_VALUE = "aab";
|
||||
|
||||
static class CommandLineOptions {
|
||||
@Option(name = "--isForCompanion", usage = "create the MIT AI2 Companion APK")
|
||||
boolean isForCompanion = false;
|
||||
|
||||
@Option(name = "--inputZipFile", required = true,
|
||||
usage = "the ZIP file of the project to build")
|
||||
usage = "the ZIP file of the project to build")
|
||||
File inputZipFile;
|
||||
|
||||
@Option(name = "--userName", required = true,
|
||||
usage = "the name of the user building the project")
|
||||
usage = "the name of the user building the project")
|
||||
String userName;
|
||||
|
||||
@Option(name = "--outputDir", required = true,
|
||||
usage = "the directory in which to put the output of the build")
|
||||
usage = "the directory in which to put the output of the build")
|
||||
File outputDir;
|
||||
|
||||
@Option(name = "--childProcessRamMb",
|
||||
usage = "Maximum ram that can be used by a child processes, in MB.")
|
||||
usage = "Maximum ram that can be used by a child processes, in MB.")
|
||||
int childProcessRamMb = 2048;
|
||||
|
||||
@Option(name = "--dexCacheDir",
|
||||
usage = "the directory to cache the pre-dexed libraries")
|
||||
usage = "the directory to cache the pre-dexed libraries")
|
||||
String dexCacheDir = null;
|
||||
|
||||
@Option(name = "--includeDangerousPermissions",
|
||||
|
@ -63,6 +66,10 @@ public final class Main {
|
|||
@Option(name = "--isForEmulator",
|
||||
usage = "Exclude native libraries for emulator.")
|
||||
boolean isForEmulator = false;
|
||||
|
||||
@Option(name = "--ext",
|
||||
usage = "Specifies the build type to use.")
|
||||
String ext = "apk";
|
||||
}
|
||||
|
||||
private static CommandLineOptions commandLineOptions = new CommandLineOptions();
|
||||
|
@ -76,7 +83,7 @@ public final class Main {
|
|||
/**
|
||||
* Main entry point.
|
||||
*
|
||||
* @param args command line arguments
|
||||
* @param args command line arguments
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
|
||||
|
@ -106,7 +113,9 @@ public final class Main {
|
|||
commandLineOptions.includeDangerousPermissions,
|
||||
commandLineOptions.extensions,
|
||||
commandLineOptions.childProcessRamMb,
|
||||
commandLineOptions.dexCacheDir, null);
|
||||
commandLineOptions.dexCacheDir,
|
||||
null,
|
||||
AAB_EXTENSION_VALUE.equals(commandLineOptions.ext));
|
||||
System.exit(result.getResult());
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// -*- mode: java; c-basic-offset: 2; -*-
|
||||
// Copyright 2009-2011 Google, All Rights reserved
|
||||
// Copyright 2011-2012 MIT, All rights reserved
|
||||
// Copyright 2011-2021 MIT, All rights reserved
|
||||
// Released under the Apache License, Version 2.0
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
package com.google.appinventor.buildserver;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// -*- mode: java; c-basic-offset: 2; -*-
|
||||
// Copyright 2009-2011 Google, All Rights reserved
|
||||
// Copyright 2011-2012 MIT, All rights reserved
|
||||
// Copyright 2011-2021 MIT, All rights reserved
|
||||
// Released under the Apache License, Version 2.0
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
package com.google.appinventor.buildserver;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// -*- mode: java; c-basic-offset: 2; -*-
|
||||
// Copyright 2009-2011 Google, All Rights reserved
|
||||
// Copyright 2011-2017 MIT, All rights reserved
|
||||
// Copyright 2011-2021 MIT, All rights reserved
|
||||
// Released under the Apache License, Version 2.0
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
package com.google.appinventor.buildserver;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// -*- mode: java; c-basic-offset: 2; -*-
|
||||
// Copyright 2009-2011 Google, All Rights reserved
|
||||
// Copyright 2011-2019 MIT, All rights reserved
|
||||
// Copyright 2011-2021 MIT, All rights reserved
|
||||
// Released under the Apache License, Version 2.0
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
|
@ -121,7 +121,7 @@ public final class ProjectBuilder {
|
|||
|
||||
Result build(String userName, ZipFile inputZip, File outputDir, String outputFileName,
|
||||
boolean isForCompanion, boolean isForEmulator, boolean includeDangerousPermissions, String[] extraExtensions,
|
||||
int childProcessRam, String dexCachePath, BuildServer.ProgressReporter reporter) {
|
||||
int childProcessRam, String dexCachePath, BuildServer.ProgressReporter reporter, boolean isAab) {
|
||||
try {
|
||||
// Download project files into a temporary directory
|
||||
File projectRoot = createNewTempDir();
|
||||
|
@ -168,7 +168,7 @@ public final class ProjectBuilder {
|
|||
boolean success =
|
||||
Compiler.compile(project, componentTypes, componentBlocks, console, console, userErrors,
|
||||
isForCompanion, isForEmulator, includeDangerousPermissions, keyStorePath,
|
||||
childProcessRam, dexCachePath, outputFileName, reporter);
|
||||
childProcessRam, dexCachePath, outputFileName, reporter, isAab);
|
||||
console.close();
|
||||
userErrors.close();
|
||||
|
||||
|
@ -181,7 +181,7 @@ public final class ProjectBuilder {
|
|||
// Locate output file
|
||||
String fileName = outputFileName;
|
||||
if (fileName == null) {
|
||||
fileName = project.getProjectName() + ".apk";
|
||||
fileName = project.getProjectName() + (isAab ? ".aab" : ".apk");
|
||||
}
|
||||
File outputFile = new File(projectRoot,
|
||||
"build/deploy/" + fileName);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// -*- mode: java; c-basic-offset: 2; -*-
|
||||
// Copyright 2009-2011 Google, All Rights reserved
|
||||
// Copyright 2011-2012 MIT, All rights reserved
|
||||
// Copyright 2011-2021 MIT, All rights reserved
|
||||
// Released under the Apache License, Version 2.0
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// -*- mode: java; c-basic-offset: 2; -*-
|
||||
// Copyright 2009-2011 Google, All Rights reserved
|
||||
// Copyright 2011-2012 MIT, All rights reserved
|
||||
// Copyright 2011-2021 MIT, All rights reserved
|
||||
// Released under the Apache License, Version 2.0
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// -*- mode: java; c-basic-offset: 2; -*-
|
||||
// Copyright 2009-2011 Google, All Rights reserved
|
||||
// Copyright 2011-2012 MIT, All rights reserved
|
||||
// Copyright 2011-2021 MIT, All rights reserved
|
||||
// Released under the Apache License, Version 2.0
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// -*- mode: java; c-basic-offset: 2; -*-
|
||||
// Copyright 2009-2011 Google, All Rights reserved
|
||||
// Copyright 2011-2012 MIT, All rights reserved
|
||||
// Copyright 2011-2021 MIT, All rights reserved
|
||||
// Released under the Apache License, Version 2.0
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
;;; Copyright 2009-2011 Google, All Rights reserved
|
||||
;;; Copyright 2011-2018 MIT, All rights reserved
|
||||
;;; Copyright 2011-2021 MIT, All rights reserved
|
||||
;;; Released under the MIT License https://raw.github.com/mit-cml/app-inventor/master/mitlicense.txt
|
||||
|
||||
;;; These are the functions that define the YAIL (Young Android Intermediate Language) runtime They
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
// -*- mode: java; c-basic-offset: 2; -*-
|
||||
// Copyright 2009-2011 Google, All Rights reserved
|
||||
// Copyright 2011-2021 MIT, All rights reserved
|
||||
// Released under the Apache License, Version 2.0
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
package com.google.appinventor.buildserver.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
/**
|
||||
* AabZipper receives the source directory with the Android App Bundle previosuly generated by
|
||||
* {@link com.google.appinventor.buildserver.AabCompiler} and builds from it a ZIP file, placed into the specified
|
||||
* destination file.
|
||||
*
|
||||
* @author diego@barreiro.xyz (Diego Barreiro)
|
||||
*/
|
||||
public class AabZipper {
|
||||
private AabZipper() {
|
||||
}
|
||||
|
||||
public static boolean zipBundle(File src, File dest, String root) {
|
||||
try (
|
||||
FileOutputStream fos = new FileOutputStream(dest);
|
||||
ZipOutputStream zipOut = new ZipOutputStream(fos);
|
||||
) {
|
||||
zipFile(src, src.getName(), zipOut, root);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void zipFile(File fileToZip, String fileName, ZipOutputStream zipOut, String root) throws IOException {
|
||||
if (fileToZip.isHidden()) {
|
||||
return;
|
||||
}
|
||||
String zipFileName = fileName;
|
||||
if (zipFileName.startsWith(root)) {
|
||||
zipFileName = zipFileName.substring(root.length());
|
||||
}
|
||||
|
||||
boolean windows = !File.separator.equals("/");
|
||||
if (windows) {
|
||||
zipFileName = zipFileName.replace(File.separator, "/");
|
||||
}
|
||||
|
||||
if (fileToZip.isDirectory()) {
|
||||
if (zipFileName.endsWith("/")) {
|
||||
zipOut.putNextEntry(new ZipEntry(zipFileName));
|
||||
} else {
|
||||
zipOut.putNextEntry(new ZipEntry(zipFileName + "/"));
|
||||
}
|
||||
zipOut.closeEntry();
|
||||
File[] children = fileToZip.listFiles();
|
||||
assert children != null;
|
||||
for (File childFile : children) {
|
||||
zipFile(childFile, fileName + File.separator + childFile.getName(), zipOut, root);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
FileInputStream fis = new FileInputStream(fileToZip);
|
||||
ZipEntry zipEntry = new ZipEntry(zipFileName);
|
||||
zipOut.putNextEntry(zipEntry);
|
||||
byte[] bytes = new byte[1024];
|
||||
int length;
|
||||
while ((length = fis.read(bytes)) >= 0) {
|
||||
zipOut.write(bytes, 0, length);
|
||||
}
|
||||
fis.close();
|
||||
}
|
||||
}
|
|
@ -168,6 +168,7 @@
|
|||
<copy toFile="${public.deps.dir}/android.jar" file="${android.lib}" />
|
||||
<copy toFile="${public.deps.dir}/dx.jar" file="${lib.dir}/android/tools/dx.jar" />
|
||||
<copy toFile="${public.deps.dir}/apksigner.jar" file="${lib.dir}/android/tools/apksigner.jar" />
|
||||
<copy toFile="${public.deps.dir}/bundletool.jar" file="${lib.dir}/android/tools/bundletool-all-0.15.0.jar" />
|
||||
<copy toFile="${public.deps.dir}/CommonVersion.jar" file="${build.dir}/common/CommonVersion.jar" />
|
||||
|
||||
<!-- Add extension libraries here -->
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -9,8 +9,6 @@ reports/
|
|||
*~
|
||||
*.out
|
||||
.DS_Store*
|
||||
*.out
|
||||
*~
|
||||
*.iml
|
||||
.idea/
|
||||
.externalToolBuilders/
|
||||
|
|
Loading…
Reference in New Issue