Cross-device sync implementation #505
396 changed files with 15157 additions and 12618 deletions
|
@ -22,6 +22,7 @@ build apk:
|
||||||
- rm -rf ~/.buildozer/android/crystax-ndk-10.3.2/platforms/android-9
|
- rm -rf ~/.buildozer/android/crystax-ndk-10.3.2/platforms/android-9
|
||||||
- ln -s ~/.buildozer/android/crystax-ndk-10.3.2/platforms/android-21 ~/.buildozer/android/crystax-ndk-10.3.2/platforms/android-9
|
- ln -s ~/.buildozer/android/crystax-ndk-10.3.2/platforms/android-21 ~/.buildozer/android/crystax-ndk-10.3.2/platforms/android-9
|
||||||
- cp -f $CI_PROJECT_DIR/scripts/build-target-python.sh ~/.buildozer/android/crystax-ndk-10.3.2/build/tools/build-target-python.sh
|
- cp -f $CI_PROJECT_DIR/scripts/build-target-python.sh ~/.buildozer/android/crystax-ndk-10.3.2/build/tools/build-target-python.sh
|
||||||
|
- cp -f $CI_PROJECT_DIR/scripts/mangled-glibc-syscalls.h ~/.buildozer/android/crystax-ndk-10.3.2/platforms/android-21/arch-arm/usr/include/crystax/bionic/libc/include/sys/mangled-glibc-syscalls.h
|
||||||
- rm ~/.buildozer/android/crystax-ndk-10.3.2-linux-x86_64.tar.xz
|
- rm ~/.buildozer/android/crystax-ndk-10.3.2-linux-x86_64.tar.xz
|
||||||
- git secret reveal
|
- git secret reveal
|
||||||
- mv buildozer.spec.travis buildozer.spec
|
- mv buildozer.spec.travis buildozer.spec
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# LBRY Android
|
# LBRY Android
|
||||||
[![Build Status](https://travis-ci.org/lbryio/lbry-android.svg?branch=master)](https://travis-ci.org/lbryio/lbry-android)
|
[![pipeline status](https://ci.lbry.tech/lbry/lbry-android/badges/master/pipeline.svg)](https://ci.lbry.tech/lbry/lbry-android/commits/master)
|
||||||
|
|
||||||
An Android browser and wallet for the [LBRY](https://lbry.io) network. This app bundles [lbrynet-daemon](https://github.com/lbryio/lbry) as a background service with a UI layer built with React Native. The APK is built using buildozer and the Gradle build tool.
|
An Android browser and wallet for the [LBRY](https://lbry.io) network. This app bundles [lbrynet-daemon](https://github.com/lbryio/lbry) as a background service with a UI layer built with React Native. The APK is built using buildozer and the Gradle build tool.
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"presets": ["react-native"],
|
"presets": ["module:metro-react-native-babel-preset"],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||||
["module-resolver", {
|
["module-resolver", {
|
||||||
root: ["./src"],
|
root: ["./src"],
|
||||||
}],
|
}],
|
||||||
|
|
7180
app/package-lock.json
generated
7180
app/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -8,23 +8,24 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"base-64": "^0.1.0",
|
"base-64": "^0.1.0",
|
||||||
"@expo/vector-icons": "^8.1.0",
|
"@expo/vector-icons": "^8.1.0",
|
||||||
"lbry-redux": "lbryio/lbry-redux",
|
"lbry-redux": "lbryio/lbry-redux#sync",
|
||||||
"lbryinc": "lbryio/lbryinc",
|
"lbryinc": "lbryio/lbryinc#sync",
|
||||||
"lodash": ">=4.17.11",
|
"lodash": ">=4.17.11",
|
||||||
"merge": ">=1.2.1",
|
"merge": ">=1.2.1",
|
||||||
"moment": "^2.22.1",
|
"moment": "^2.22.1",
|
||||||
"react": "16.2.0",
|
"react": "16.8.6",
|
||||||
"react-native": "0.55.3",
|
"react-native": "0.59.3",
|
||||||
|
"@react-native-community/async-storage": "^1.2.2",
|
||||||
"react-native-country-picker-modal": "^0.6.2",
|
"react-native-country-picker-modal": "^0.6.2",
|
||||||
"react-native-exception-handler": "2.9.0",
|
"react-native-exception-handler": "2.9.0",
|
||||||
"react-native-fast-image": "^5.0.3",
|
"react-native-fast-image": "^5.0.3",
|
||||||
"rn-fetch-blob": "^0.10.15",
|
"react-native-gesture-handler": "^1.1.0",
|
||||||
"react-native-image-zoom-viewer": "^2.2.5",
|
"react-native-image-zoom-viewer": "^2.2.5",
|
||||||
"react-native-phone-input": "lbryio/react-native-phone-input",
|
"react-native-phone-input": "lbryio/react-native-phone-input",
|
||||||
"react-native-vector-icons": "^5.0.0",
|
"react-native-vector-icons": "^5.0.0",
|
||||||
"react-native-video": "lbryio/react-native-video#exoplayer-lbry-android",
|
"react-native-video": "lbryio/react-native-video#exoplayer-lbry-android",
|
||||||
"react-navigation": "^2.18.3",
|
"react-navigation": "^3.6.1",
|
||||||
"react-navigation-redux-helpers": "^2.0.9",
|
"react-navigation-redux-helpers": "^3.0.0",
|
||||||
"react-redux": "^5.0.3",
|
"react-redux": "^5.0.3",
|
||||||
"redux": "^3.6.0",
|
"redux": "^3.6.0",
|
||||||
"redux-logger": "3.0.6",
|
"redux-logger": "3.0.6",
|
||||||
|
@ -32,10 +33,13 @@
|
||||||
"redux-persist-filesystem-storage": "^1.3.2",
|
"redux-persist-filesystem-storage": "^1.3.2",
|
||||||
"redux-persist-transform-compress": "^4.2.0",
|
"redux-persist-transform-compress": "^4.2.0",
|
||||||
"redux-persist-transform-filter": "0.0.10",
|
"redux-persist-transform-filter": "0.0.10",
|
||||||
"redux-thunk": "^2.2.0"
|
"redux-thunk": "^2.2.0",
|
||||||
|
"rn-fetch-blob": "^0.10.15"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.4.3",
|
||||||
"babel-preset-env": "^1.6.1",
|
"babel-preset-env": "^1.6.1",
|
||||||
|
"babel-preset-react-native": "5.0.2",
|
||||||
"babel-preset-stage-2": "^6.18.0",
|
"babel-preset-stage-2": "^6.18.0",
|
||||||
"babel-plugin-module-resolver": "^3.1.1",
|
"babel-plugin-module-resolver": "^3.1.1",
|
||||||
"flow-babel-webpack-plugin": "^1.1.1"
|
"flow-babel-webpack-plugin": "^1.1.1"
|
||||||
|
|
|
@ -14,19 +14,19 @@ import TransactionHistoryPage from 'page/transactionHistory';
|
||||||
import WalletPage from 'page/wallet';
|
import WalletPage from 'page/wallet';
|
||||||
import SearchInput from 'component/searchInput';
|
import SearchInput from 'component/searchInput';
|
||||||
import {
|
import {
|
||||||
|
createAppContainer,
|
||||||
createDrawerNavigator,
|
createDrawerNavigator,
|
||||||
createStackNavigator,
|
createStackNavigator,
|
||||||
NavigationActions
|
NavigationActions
|
||||||
} from 'react-navigation';
|
} from 'react-navigation';
|
||||||
import {
|
import {
|
||||||
addListener,
|
addListener,
|
||||||
reduxifyNavigator,
|
createReduxContainer,
|
||||||
createReactNavigationReduxMiddleware,
|
createReactNavigationReduxMiddleware,
|
||||||
} from 'react-navigation-redux-helpers';
|
} from 'react-navigation-redux-helpers';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
AppState,
|
AppState,
|
||||||
AsyncStorage,
|
|
||||||
BackHandler,
|
BackHandler,
|
||||||
Linking,
|
Linking,
|
||||||
NativeModules,
|
NativeModules,
|
||||||
|
@ -37,6 +37,7 @@ import { doDeleteCompleteBlobs } from 'redux/actions/file';
|
||||||
import { selectDrawerStack } from 'redux/selectors/drawer';
|
import { selectDrawerStack } from 'redux/selectors/drawer';
|
||||||
import { SETTINGS, doDismissToast, doToast, selectToast } from 'lbry-redux';
|
import { SETTINGS, doDismissToast, doToast, selectToast } from 'lbry-redux';
|
||||||
import {
|
import {
|
||||||
|
doGetSync,
|
||||||
doUserCheckEmailVerified,
|
doUserCheckEmailVerified,
|
||||||
doUserEmailVerify,
|
doUserEmailVerify,
|
||||||
doUserEmailVerifyFailure,
|
doUserEmailVerifyFailure,
|
||||||
|
@ -48,6 +49,7 @@ import {
|
||||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import { decode as atob } from 'base-64';
|
import { decode as atob } from 'base-64';
|
||||||
import { dispatchNavigateBack, dispatchNavigateToUri } from 'utils/helper';
|
import { dispatchNavigateBack, dispatchNavigateToUri } from 'utils/helper';
|
||||||
|
import AsyncStorage from '@react-native-community/async-storage';
|
||||||
import Colors from 'styles/colors';
|
import Colors from 'styles/colors';
|
||||||
import Constants from 'constants';
|
import Constants from 'constants';
|
||||||
import Icon from 'react-native-vector-icons/FontAwesome5';
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
|
@ -195,7 +197,7 @@ const drawer = createDrawerNavigator({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AppNavigator = new createStackNavigator({
|
const mainStackNavigator = new createStackNavigator({
|
||||||
FirstRun: {
|
FirstRun: {
|
||||||
screen: FirstRunScreen,
|
screen: FirstRunScreen,
|
||||||
navigationOptions: {
|
navigationOptions: {
|
||||||
|
@ -214,12 +216,11 @@ export const AppNavigator = new createStackNavigator({
|
||||||
}, {
|
}, {
|
||||||
headerMode: 'none'
|
headerMode: 'none'
|
||||||
});
|
});
|
||||||
|
export const AppNavigator = mainStackNavigator;
|
||||||
export const reactNavigationMiddleware = createReactNavigationReduxMiddleware(
|
export const reactNavigationMiddleware = createReactNavigationReduxMiddleware(
|
||||||
"root",
|
|
||||||
state => state.nav,
|
state => state.nav,
|
||||||
);
|
);
|
||||||
const App = reduxifyNavigator(AppNavigator, "root");
|
const App = createReduxContainer(mainStackNavigator, "root");
|
||||||
const appMapStateToProps = (state) => ({
|
const appMapStateToProps = (state) => ({
|
||||||
state: state.nav,
|
state: state.nav,
|
||||||
});
|
});
|
||||||
|
@ -280,13 +281,18 @@ class AppWithNavigationState extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
const { user } = this.props;
|
const { dispatch, user } = this.props;
|
||||||
if (this.state.verifyPending && this.emailVerifyCheckInterval > 0 && user && user.has_verified_email) {
|
if (this.state.verifyPending && this.emailVerifyCheckInterval > 0 && user && user.has_verified_email) {
|
||||||
clearInterval(this.emailVerifyCheckInterval);
|
clearInterval(this.emailVerifyCheckInterval);
|
||||||
AsyncStorage.setItem(Constants.KEY_EMAIL_VERIFY_PENDING, 'false');
|
AsyncStorage.setItem(Constants.KEY_EMAIL_VERIFY_PENDING, 'false');
|
||||||
this.setState({ verifyPending: false });
|
this.setState({ verifyPending: false });
|
||||||
|
|
||||||
ToastAndroid.show('Your email address was successfully verified.', ToastAndroid.LONG);
|
ToastAndroid.show('Your email address was successfully verified.', ToastAndroid.LONG);
|
||||||
|
|
||||||
|
// upon successful email verification, check wallet sync
|
||||||
|
NativeModules.UtilityModule.getSecureValue(Constants.KEY_FIRST_RUN_PASSWORD).then(walletPassword => {
|
||||||
|
dispatch(doGetSync(walletPassword));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
doUserEmailNew,
|
doUserEmailNew,
|
||||||
|
doUserEmailToVerify,
|
||||||
doUserResendVerificationEmail,
|
doUserResendVerificationEmail,
|
||||||
selectEmailNewErrorMessage,
|
selectEmailNewErrorMessage,
|
||||||
selectEmailNewIsPending,
|
selectEmailNewIsPending,
|
||||||
|
@ -17,6 +18,7 @@ const select = state => ({
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
addUserEmail: email => dispatch(doUserEmailNew(email)),
|
addUserEmail: email => dispatch(doUserEmailNew(email)),
|
||||||
|
setEmailToVerify: email => dispatch(doUserEmailToVerify(email)),
|
||||||
notify: data => dispatch(doToast(data)),
|
notify: data => dispatch(doToast(data)),
|
||||||
resendVerificationEmail: email => dispatch(doUserResendVerificationEmail(email))
|
resendVerificationEmail: email => dispatch(doUserResendVerificationEmail(email))
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,42 +2,43 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
AsyncStorage,
|
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View
|
View
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
import AsyncStorage from '@react-native-community/async-storage';
|
||||||
import Icon from 'react-native-vector-icons/FontAwesome5';
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
import Button from '../button';
|
import Button from 'component/button';
|
||||||
import Colors from '../../styles/colors';
|
import Colors from 'styles/colors';
|
||||||
import Constants from '../../constants';
|
import Constants from 'constants';
|
||||||
import Link from '../link';
|
import Link from 'component/link';
|
||||||
import rewardStyle from '../../styles/reward';
|
import rewardStyle from 'styles/reward';
|
||||||
|
|
||||||
class EmailRewardSubcard extends React.PureComponent {
|
class EmailRewardSubcard extends React.PureComponent {
|
||||||
state = {
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
email: null,
|
email: null,
|
||||||
emailAlreadySet: false,
|
emailAlreadySet: false,
|
||||||
previousEmail: null,
|
previousEmail: null,
|
||||||
verfiyStarted: false
|
verfiyStarted: false
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { emailToVerify } = this.props;
|
const { setEmailToVerify } = this.props;
|
||||||
AsyncStorage.getItem(Constants.KEY_FIRST_RUN_EMAIL).then(email => {
|
AsyncStorage.getItem(Constants.KEY_FIRST_RUN_EMAIL).then(email => setEmailToVerify(email));
|
||||||
if (email && email.trim().length > 0) {
|
|
||||||
this.setState({ email, emailAlreadySet: true, previousEmail: email });
|
|
||||||
} else {
|
|
||||||
this.setState({ email: emailToVerify, previousEmail: emailToVerify });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
const { emailNewErrorMessage, emailNewPending } = nextProps;
|
const { emailNewErrorMessage, emailNewPending, emailToVerify } = nextProps;
|
||||||
const { notify } = this.props;
|
const { notify } = this.props;
|
||||||
|
|
||||||
|
if (emailToVerify && emailToVerify.trim().length > 0 && !this.state.email && !this.state.previousEmail) {
|
||||||
|
this.setState({ email: emailToVerify, previousEmail: emailToVerify, emailAlreadySet: true });
|
||||||
|
}
|
||||||
|
|
||||||
if (this.state.verifyStarted && !emailNewPending) {
|
if (this.state.verifyStarted && !emailNewPending) {
|
||||||
if (emailNewErrorMessage) {
|
if (emailNewErrorMessage) {
|
||||||
notify({ message: String(emailNewErrorMessage), isError: true });
|
notify({ message: String(emailNewErrorMessage), isError: true });
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectBalance } from 'lbry-redux';
|
import { selectTotalBalance } from 'lbry-redux';
|
||||||
|
import { selectUnclaimedRewardValue } from 'lbryinc';
|
||||||
import FloatingWalletBalance from './view';
|
import FloatingWalletBalance from './view';
|
||||||
import { doRewardList, selectUnclaimedRewardValue, selectFetchingRewards, selectUser } from 'lbryinc';
|
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
balance: selectBalance(state),
|
balance: selectTotalBalance(state),
|
||||||
unclaimedRewardAmount: selectUnclaimedRewardValue(state),
|
unclaimedRewardAmount: selectUnclaimedRewardValue(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -18,19 +18,18 @@ class FloatingWalletBalance extends React.PureComponent<Props> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[floatingButtonStyle.view, floatingButtonStyle.bottomRight]}>
|
<View style={[floatingButtonStyle.view, floatingButtonStyle.bottomRight]}>
|
||||||
<TouchableOpacity style={floatingButtonStyle.container}
|
|
||||||
onPress={() => navigation && navigation.navigate({ routeName: 'WalletStack' })}>
|
|
||||||
{isNaN(balance) && <ActivityIndicator size="small" color={Colors.White} />}
|
|
||||||
<Text style={floatingButtonStyle.text}>
|
|
||||||
{(balance || balance === 0) && (formatCredits(parseFloat(balance), 2) + ' LBC')}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
{unclaimedRewardAmount > 0 &&
|
{unclaimedRewardAmount > 0 &&
|
||||||
<TouchableOpacity style={floatingButtonStyle.pendingContainer}
|
<TouchableOpacity style={floatingButtonStyle.pendingContainer}
|
||||||
onPress={() => navigation && navigation.navigate({ routeName: 'Rewards' })} >
|
onPress={() => navigation && navigation.navigate({ routeName: 'Rewards' })} >
|
||||||
<Icon name="award" size={18} style={floatingButtonStyle.rewardIcon} />
|
<Icon name="award" size={18} style={floatingButtonStyle.rewardIcon} />
|
||||||
<Text style={floatingButtonStyle.text}>{unclaimedRewardAmount}</Text>
|
<Text style={floatingButtonStyle.text}>{unclaimedRewardAmount}</Text>
|
||||||
</TouchableOpacity>}
|
</TouchableOpacity>}
|
||||||
|
<TouchableOpacity style={floatingButtonStyle.container}
|
||||||
|
onPress={() => navigation && navigation.navigate({ routeName: 'WalletStack' })}>
|
||||||
|
{isNaN(balance) && <ActivityIndicator size="small" color={Colors.White} />}
|
||||||
|
{(!isNaN(balance) || balance === 0) && (
|
||||||
|
<Text style={floatingButtonStyle.text}>{(formatCredits(parseFloat(balance), 2) + ' LBC')}</Text>)}
|
||||||
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
AsyncStorage,
|
|
||||||
DeviceEventEmitter,
|
DeviceEventEmitter,
|
||||||
NativeModules,
|
NativeModules,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
|
@ -11,6 +10,7 @@ import {
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View
|
View
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
import AsyncStorage from '@react-native-community/async-storage';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import Colors from 'styles/colors';
|
import Colors from 'styles/colors';
|
||||||
import Constants from 'constants';
|
import Constants from 'constants';
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { AsyncStorage, NativeModules, Text, TouchableOpacity, View } from 'react-native';
|
import { NativeModules, Text, TouchableOpacity, View } from 'react-native';
|
||||||
import Button from '../../component/button';
|
import AsyncStorage from '@react-native-community/async-storage';
|
||||||
import rewardStyle from '../../styles/reward';
|
import Button from 'component/button';
|
||||||
|
import rewardStyle from 'styles/reward';
|
||||||
|
|
||||||
class RewardSummary extends React.Component {
|
class RewardSummary extends React.Component {
|
||||||
static itemKey = 'rewardSummaryDismissed';
|
static itemKey = 'rewardSummaryDismissed';
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ActivityIndicator, SectionList, Text, View } from 'react-native';
|
import { ActivityIndicator, SectionList, Text, View } from 'react-native';
|
||||||
import { normalizeURI } from 'lbry-redux';
|
import { normalizeURI } from 'lbry-redux';
|
||||||
|
import { navigateToUri } from 'utils/helper';
|
||||||
import SubscribeButton from 'component/subscribeButton';
|
import SubscribeButton from 'component/subscribeButton';
|
||||||
import SuggestedSubscriptionItem from 'component/suggestedSubscriptionItem';
|
import SuggestedSubscriptionItem from 'component/suggestedSubscriptionItem';
|
||||||
import Colors from 'styles/colors';
|
import Colors from 'styles/colors';
|
||||||
|
@ -36,7 +37,9 @@ class SuggestedSubscriptions extends React.PureComponent {
|
||||||
const channelUri = normalizeURI(titleParts[1]);
|
const channelUri = normalizeURI(titleParts[1]);
|
||||||
return (
|
return (
|
||||||
<View style={subscriptionsStyle.titleRow}>
|
<View style={subscriptionsStyle.titleRow}>
|
||||||
<Link style={subscriptionsStyle.channelTitle} text={channelName} href={channelUri} />
|
<Link style={subscriptionsStyle.channelTitle} text={channelName} onPress={() => {
|
||||||
|
navigateToUri(navigation, normalizeURI(channelUri));
|
||||||
|
}} />
|
||||||
<SubscribeButton style={subscriptionsStyle.subscribeButton} uri={channelUri} name={channelName} />
|
<SubscribeButton style={subscriptionsStyle.subscribeButton} uri={channelUri} name={channelName} />
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
|
@ -101,9 +101,9 @@ class UriBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
let style = [uriBarStyle.overlay];
|
let style = [uriBarStyle.overlay];
|
||||||
if (this.state.focused) {
|
|
||||||
style.push(uriBarStyle.inFocus);
|
// TODO: Add optional setting to enable URI / search bar suggestions
|
||||||
}
|
/*if (this.state.focused) { style.push(uriBarStyle.inFocus); }*/
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={style}>
|
<View style={style}>
|
||||||
|
@ -152,7 +152,6 @@ class UriBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}/>
|
}}/>
|
||||||
</View>
|
|
||||||
{(this.state.focused && !this.state.directSearch) && (
|
{(this.state.focused && !this.state.directSearch) && (
|
||||||
<View style={uriBarStyle.suggestions}>
|
<View style={uriBarStyle.suggestions}>
|
||||||
<FlatList style={uriBarStyle.suggestionList}
|
<FlatList style={uriBarStyle.suggestionList}
|
||||||
|
@ -167,6 +166,7 @@ class UriBar extends React.PureComponent {
|
||||||
/>)} />
|
/>)} />
|
||||||
</View>)}
|
</View>)}
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectBalance } from 'lbry-redux';
|
import { selectTotalBalance } from 'lbry-redux';
|
||||||
import WalletBalance from './view';
|
import WalletBalance from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
balance: selectBalance(state),
|
balance: selectTotalBalance(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, null)(WalletBalance);
|
export default connect(select, null)(WalletBalance);
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Image, Text, View } from 'react-native';
|
import { Image, Text, View } from 'react-native';
|
||||||
import { formatCredits } from 'lbry-redux'
|
import { Lbry, formatCredits } from 'lbry-redux'
|
||||||
import Address from '../address';
|
import Address from 'component/address';
|
||||||
import Button from '../button';
|
import Button from 'component/button';
|
||||||
import walletStyle from '../../styles/wallet';
|
import walletStyle from 'styles/wallet';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
balance: number,
|
balance: number,
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
const Constants = {
|
const Constants = {
|
||||||
|
FIRST_RUN_PAGE_WELCOME: "welcome",
|
||||||
|
FIRST_RUN_PAGE_EMAIL_COLLECT: "email-collect",
|
||||||
|
FIRST_RUN_PAGE_WALLET: "wallet",
|
||||||
|
FIRST_RUN_PAGE_SKIP_ACCOUNT: "skip-account",
|
||||||
|
|
||||||
KEY_FIRST_RUN_EMAIL: "firstRunEmail",
|
KEY_FIRST_RUN_EMAIL: "firstRunEmail",
|
||||||
|
KEY_FIRST_RUN_PASSWORD: "firstRunPassword",
|
||||||
KEY_SHOULD_VERIFY_EMAIL: "shouldVerifyEmail",
|
KEY_SHOULD_VERIFY_EMAIL: "shouldVerifyEmail",
|
||||||
KEY_EMAIL_VERIFY_PENDING: "emailVerifyPending",
|
KEY_EMAIL_VERIFY_PENDING: "emailVerifyPending",
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { Provider, connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
AppRegistry,
|
AppRegistry,
|
||||||
AppState,
|
AppState,
|
||||||
AsyncStorage,
|
|
||||||
Text,
|
Text,
|
||||||
View,
|
View,
|
||||||
NativeModules
|
NativeModules
|
||||||
|
@ -25,6 +24,7 @@ import {
|
||||||
homepageReducer,
|
homepageReducer,
|
||||||
rewardsReducer,
|
rewardsReducer,
|
||||||
subscriptionsReducer,
|
subscriptionsReducer,
|
||||||
|
syncReducer,
|
||||||
userReducer
|
userReducer
|
||||||
} from 'lbryinc';
|
} from 'lbryinc';
|
||||||
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
|
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
|
||||||
|
@ -32,6 +32,7 @@ import { createLogger } from 'redux-logger';
|
||||||
import { AppNavigator } from 'component/AppNavigator';
|
import { AppNavigator } from 'component/AppNavigator';
|
||||||
import { persistStore, autoRehydrate } from 'redux-persist';
|
import { persistStore, autoRehydrate } from 'redux-persist';
|
||||||
import AppWithNavigationState, { reactNavigationMiddleware } from './component/AppNavigator';
|
import AppWithNavigationState, { reactNavigationMiddleware } from './component/AppNavigator';
|
||||||
|
import AsyncStorage from '@react-native-community/async-storage';
|
||||||
import FilesystemStorage from 'redux-persist-filesystem-storage';
|
import FilesystemStorage from 'redux-persist-filesystem-storage';
|
||||||
import createCompressor from 'redux-persist-transform-compress';
|
import createCompressor from 'redux-persist-transform-compress';
|
||||||
import createFilter from 'redux-persist-transform-filter';
|
import createFilter from 'redux-persist-transform-filter';
|
||||||
|
@ -40,6 +41,7 @@ import drawerReducer from 'redux/reducers/drawer';
|
||||||
import settingsReducer from 'redux/reducers/settings';
|
import settingsReducer from 'redux/reducers/settings';
|
||||||
import thunk from 'redux-thunk';
|
import thunk from 'redux-thunk';
|
||||||
|
|
||||||
|
|
||||||
const globalExceptionHandler = (error, isFatal) => {
|
const globalExceptionHandler = (error, isFatal) => {
|
||||||
if (error && NativeModules.Mixpanel) {
|
if (error && NativeModules.Mixpanel) {
|
||||||
NativeModules.Mixpanel.logException(isFatal, error.message ? error.message : "No message", error);
|
NativeModules.Mixpanel.logException(isFatal, error.message ? error.message : "No message", error);
|
||||||
|
@ -47,7 +49,6 @@ const globalExceptionHandler = (error, isFatal) => {
|
||||||
};
|
};
|
||||||
setJSExceptionHandler(globalExceptionHandler, true);
|
setJSExceptionHandler(globalExceptionHandler, true);
|
||||||
|
|
||||||
|
|
||||||
function isFunction(object) {
|
function isFunction(object) {
|
||||||
return typeof object === 'function';
|
return typeof object === 'function';
|
||||||
}
|
}
|
||||||
|
@ -99,6 +100,7 @@ const reducers = combineReducers({
|
||||||
settings: settingsReducer,
|
settings: settingsReducer,
|
||||||
search: searchReducer,
|
search: searchReducer,
|
||||||
subscriptions: subscriptionsReducer,
|
subscriptions: subscriptionsReducer,
|
||||||
|
sync: syncReducer,
|
||||||
user: userReducer,
|
user: userReducer,
|
||||||
wallet: walletReducer
|
wallet: walletReducer
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,6 @@ import NavigationActions from 'react-navigation';
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
AsyncStorage,
|
|
||||||
Linking,
|
Linking,
|
||||||
NativeModules,
|
NativeModules,
|
||||||
SectionList,
|
SectionList,
|
||||||
|
@ -11,6 +10,7 @@ import {
|
||||||
View
|
View
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { Lbry, normalizeURI, parseURI } from 'lbry-redux';
|
import { Lbry, normalizeURI, parseURI } from 'lbry-redux';
|
||||||
|
import AsyncStorage from '@react-native-community/async-storage';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import Constants from 'constants';
|
import Constants from 'constants';
|
||||||
import Colors from 'styles/colors';
|
import Colors from 'styles/colors';
|
||||||
|
|
|
@ -591,6 +591,10 @@ class FilePage extends React.PureComponent {
|
||||||
show={DateTime.SHOW_DATE} />
|
show={DateTime.SHOW_DATE} />
|
||||||
</View>
|
</View>
|
||||||
<View style={filePageStyle.subscriptionRow}>
|
<View style={filePageStyle.subscriptionRow}>
|
||||||
|
<Button style={[filePageStyle.actionButton, filePageStyle.tipButton]}
|
||||||
|
theme={"light"}
|
||||||
|
icon={"gift"}
|
||||||
|
onPress={() => this.setState({ showTipView: true })} />
|
||||||
<SubscribeButton
|
<SubscribeButton
|
||||||
style={filePageStyle.actionButton}
|
style={filePageStyle.actionButton}
|
||||||
uri={fullChannelUri}
|
uri={fullChannelUri}
|
||||||
|
@ -603,14 +607,9 @@ class FilePage extends React.PureComponent {
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
|
|
||||||
{(this.state.showDescription && description && description.length > 0) && <View style={filePageStyle.divider} />}
|
{this.state.showTipView && <View style={filePageStyle.divider} />}
|
||||||
{(this.state.showDescription && description) &&
|
{this.state.showTipView &&
|
||||||
<Text style={filePageStyle.description} selectable={true}>{this.linkify(description)}</Text>}
|
<View style={filePageStyle.tipCard}>
|
||||||
|
|
||||||
<View onLayout={this.setRelatedContentPosition} />
|
|
||||||
<RelatedContent navigation={navigation} uri={uri} />
|
|
||||||
</ScrollView>
|
|
||||||
{this.state.showTipView && <View style={filePageStyle.tipCard}>
|
|
||||||
<View style={filePageStyle.row}>
|
<View style={filePageStyle.row}>
|
||||||
<View style={filePageStyle.amountRow}>
|
<View style={filePageStyle.amountRow}>
|
||||||
<TextInput ref={ref => this.tipAmountInput = ref}
|
<TextInput ref={ref => this.tipAmountInput = ref}
|
||||||
|
@ -621,12 +620,20 @@ class FilePage extends React.PureComponent {
|
||||||
<Text style={[filePageStyle.text, filePageStyle.currency]}>LBC</Text>
|
<Text style={[filePageStyle.text, filePageStyle.currency]}>LBC</Text>
|
||||||
</View>
|
</View>
|
||||||
<Link style={[filePageStyle.link, filePageStyle.cancelTipLink]} text={'Cancel'} onPress={() => this.setState({ showTipView: false })} />
|
<Link style={[filePageStyle.link, filePageStyle.cancelTipLink]} text={'Cancel'} onPress={() => this.setState({ showTipView: false })} />
|
||||||
<Button text={'Send tip'}
|
<Button text={'Send a tip'}
|
||||||
style={[filePageStyle.button, filePageStyle.sendButton]}
|
style={[filePageStyle.button, filePageStyle.sendButton]}
|
||||||
disabled={!canSendTip}
|
disabled={!canSendTip}
|
||||||
onPress={this.handleSendTip} />
|
onPress={this.handleSendTip} />
|
||||||
</View>
|
</View>
|
||||||
</View>}
|
</View>}
|
||||||
|
|
||||||
|
{(this.state.showDescription && description && description.length > 0) && <View style={filePageStyle.divider} />}
|
||||||
|
{(this.state.showDescription && description) &&
|
||||||
|
<Text style={filePageStyle.description} selectable={true}>{this.linkify(description)}</Text>}
|
||||||
|
|
||||||
|
<View onLayout={this.setRelatedContentPosition} />
|
||||||
|
<RelatedContent navigation={navigation} uri={uri} />
|
||||||
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{!this.state.fullscreenMode && <FloatingWalletBalance navigation={navigation} />}
|
{!this.state.fullscreenMode && <FloatingWalletBalance navigation={navigation} />}
|
||||||
|
|
|
@ -2,7 +2,6 @@ import React from 'react';
|
||||||
import { Lbry } from 'lbry-redux';
|
import { Lbry } from 'lbry-redux';
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
AsyncStorage,
|
|
||||||
Linking,
|
Linking,
|
||||||
NativeModules,
|
NativeModules,
|
||||||
Platform,
|
Platform,
|
||||||
|
@ -10,9 +9,10 @@ import {
|
||||||
TextInput,
|
TextInput,
|
||||||
View
|
View
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import Colors from '../../../styles/colors';
|
import AsyncStorage from '@react-native-community/async-storage';
|
||||||
import Constants from '../../../constants';
|
import Colors from 'styles/colors';
|
||||||
import firstRunStyle from '../../../styles/firstRun';
|
import Constants from 'constants';
|
||||||
|
import firstRunStyle from 'styles/firstRun';
|
||||||
|
|
||||||
class EmailCollectPage extends React.PureComponent {
|
class EmailCollectPage extends React.PureComponent {
|
||||||
static MAX_STATUS_TRIES = 30;
|
static MAX_STATUS_TRIES = 30;
|
||||||
|
@ -52,7 +52,7 @@ class EmailCollectPage extends React.PureComponent {
|
||||||
this.setState({ authenticationStarted: true, authenticationFailed: false });
|
this.setState({ authenticationStarted: true, authenticationFailed: false });
|
||||||
NativeModules.VersionInfo.getAppVersion().then(appVersion => {
|
NativeModules.VersionInfo.getAppVersion().then(appVersion => {
|
||||||
Lbry.status().then(info => {
|
Lbry.status().then(info => {
|
||||||
authenticate(appVersion, Platform.OS)
|
authenticate(appVersion, Platform.OS);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
if (this.state.statusTries >= EmailCollectPage.MAX_STATUS_TRIES) {
|
if (this.state.statusTries >= EmailCollectPage.MAX_STATUS_TRIES) {
|
||||||
this.setState({ authenticationFailed: true });
|
this.setState({ authenticationFailed: true });
|
||||||
|
@ -98,9 +98,7 @@ class EmailCollectPage extends React.PureComponent {
|
||||||
} else {
|
} else {
|
||||||
content = (
|
content = (
|
||||||
<View onLayout={onEmailViewLayout}>
|
<View onLayout={onEmailViewLayout}>
|
||||||
<Text style={firstRunStyle.title}>Rewards.</Text>
|
<Text style={firstRunStyle.title}>Setup account</Text>
|
||||||
<Text style={firstRunStyle.paragraph}>You can earn LBRY Credits (LBC) rewards by completing various tasks in the app.</Text>
|
|
||||||
<Text style={firstRunStyle.paragraph}>Please provide a valid email address below to be able to claim your rewards.</Text>
|
|
||||||
<TextInput style={firstRunStyle.emailInput}
|
<TextInput style={firstRunStyle.emailInput}
|
||||||
placeholder={this.state.placeholder}
|
placeholder={this.state.placeholder}
|
||||||
underlineColorAndroid="transparent"
|
underlineColorAndroid="transparent"
|
||||||
|
@ -117,7 +115,8 @@ class EmailCollectPage extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Text style={firstRunStyle.infoParagraph}>This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to earn LBRY rewards and may be used to sync usage data across devices.</Text>
|
<Text style={firstRunStyle.paragraph}>An account will allow you to earn rewards and keep your content and settings synced.</Text>
|
||||||
|
<Text style={firstRunStyle.infoParagraph}>This information is disclosed only to LBRY, Inc. and not to the LBRY network.</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
52
app/src/page/firstRun/internal/skip-account-page.js
Normal file
52
app/src/page/firstRun/internal/skip-account-page.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Lbry } from 'lbry-redux';
|
||||||
|
import {
|
||||||
|
ActivityIndicator,
|
||||||
|
AsyncStorage,
|
||||||
|
Linking,
|
||||||
|
NativeModules,
|
||||||
|
Platform,
|
||||||
|
Switch,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
View
|
||||||
|
} from 'react-native';
|
||||||
|
import Colors from 'styles/colors';
|
||||||
|
import Constants from 'constants';
|
||||||
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
|
import firstRunStyle from 'styles/firstRun';
|
||||||
|
|
||||||
|
class SkipAccountPage extends React.PureComponent {
|
||||||
|
state = {
|
||||||
|
confirmed: false
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { onSkipAccountViewLayout, onSkipSwitchChanged } = this.props;
|
||||||
|
|
||||||
|
const content = (
|
||||||
|
<View onLayout={onSkipAccountViewLayout}>
|
||||||
|
<View style={firstRunStyle.row}>
|
||||||
|
<Icon name="exclamation-triangle" style={firstRunStyle.titleIcon} size={32} color={Colors.White} />
|
||||||
|
<Text style={firstRunStyle.title}>Are you sure?</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={firstRunStyle.paragraph}>Without an account, you will not receive rewards, sync and backup services, or security updates.</Text>
|
||||||
|
|
||||||
|
<View style={[firstRunStyle.row, firstRunStyle.confirmContainer]}>
|
||||||
|
<View style={firstRunStyle.rowSwitch}>
|
||||||
|
<Switch value={this.state.confirmed} onValueChange={value => { this.setState({ confirmed: value }); onSkipSwitchChanged(value); }} />
|
||||||
|
</View>
|
||||||
|
<Text style={firstRunStyle.rowParagraph}>I understand that by uninstalling LBRY I will lose any balances or published content with no recovery option.</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={firstRunStyle.container}>
|
||||||
|
{content}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SkipAccountPage;
|
75
app/src/page/firstRun/internal/wallet-page.js
Normal file
75
app/src/page/firstRun/internal/wallet-page.js
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Lbry } from 'lbry-redux';
|
||||||
|
import {
|
||||||
|
ActivityIndicator,
|
||||||
|
Linking,
|
||||||
|
NativeModules,
|
||||||
|
Platform,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
View
|
||||||
|
} from 'react-native';
|
||||||
|
import AsyncStorage from '@react-native-community/async-storage';
|
||||||
|
import Colors from 'styles/colors';
|
||||||
|
import Constants from 'constants';
|
||||||
|
import firstRunStyle from 'styles/firstRun';
|
||||||
|
|
||||||
|
class WalletPage extends React.PureComponent {
|
||||||
|
state = {
|
||||||
|
password: null,
|
||||||
|
placeholder: 'password',
|
||||||
|
statusTries: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
handleChangeText = (text) => {
|
||||||
|
// save the value to the state email
|
||||||
|
const { onPasswordChanged } = this.props;
|
||||||
|
this.setState({ password: text });
|
||||||
|
if (onPasswordChanged) {
|
||||||
|
onPasswordChanged(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NativeModules.UtilityModule) {
|
||||||
|
NativeModules.UtilityModule.setSecureValue(Constants.KEY_FIRST_RUN_PASSWORD, text);
|
||||||
|
// simply set any string value to indicate that a passphrase was set on first run
|
||||||
|
AsyncStorage.setItem(Constants.KEY_FIRST_RUN_PASSWORD, "true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { onPasswordChanged, onWalletViewLayout } = this.props;
|
||||||
|
|
||||||
|
const content = (
|
||||||
|
<View onLayout={onWalletViewLayout}>
|
||||||
|
<Text style={firstRunStyle.title}>Password</Text>
|
||||||
|
<Text style={firstRunStyle.paragraph}>Please enter a password to secure your account and wallet.</Text>
|
||||||
|
<TextInput style={firstRunStyle.passwordInput}
|
||||||
|
placeholder={this.state.placeholder}
|
||||||
|
underlineColorAndroid="transparent"
|
||||||
|
secureTextEntry={true}
|
||||||
|
value={this.state.password}
|
||||||
|
onChangeText={text => this.handleChangeText(text)}
|
||||||
|
onFocus={() => {
|
||||||
|
if (!this.state.password || this.state.password.length === 0) {
|
||||||
|
this.setState({ placeholder: '' });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
if (!this.state.password || this.state.password.length === 0) {
|
||||||
|
this.setState({ placeholder: 'password' });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text style={firstRunStyle.infoParagraph}>Note: for wallet security purposes, LBRY is unable to reset your password.</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={firstRunStyle.container}>
|
||||||
|
{content}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WalletPage;
|
|
@ -1,18 +1,15 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Lbry } from 'lbry-redux';
|
import { Lbry } from 'lbry-redux';
|
||||||
import { View, Text, Linking } from 'react-native';
|
import { View, Text, Linking } from 'react-native';
|
||||||
import Colors from '../../../styles/colors';
|
import Colors from 'styles/colors';
|
||||||
import firstRunStyle from '../../../styles/firstRun';
|
import firstRunStyle from 'styles/firstRun';
|
||||||
|
|
||||||
class WelcomePage extends React.PureComponent {
|
class WelcomePage extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<View style={firstRunStyle.container}>
|
<View style={firstRunStyle.container}>
|
||||||
<Text style={firstRunStyle.title}>Welcome to LBRY.</Text>
|
<Text style={firstRunStyle.title}>Welcome to LBRY.</Text>
|
||||||
<Text style={firstRunStyle.paragraph}>LBRY is a decentralized peer-to-peer content sharing platform where
|
<Text style={firstRunStyle.paragraph}>LBRY is a community-controlled content platform where you can find and publish videos, music, books, and more.</Text>
|
||||||
you can upload and download videos, music, ebooks and other forms of digital content.</Text>
|
|
||||||
<Text style={firstRunStyle.paragraph}>We make use of a blockchain which needs to be synchronized before
|
|
||||||
you can use the app. Synchronization may take a while because this is the first app launch.</Text>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ import React from 'react';
|
||||||
import { Lbry } from 'lbry-redux';
|
import { Lbry } from 'lbry-redux';
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
AsyncStorage,
|
|
||||||
Linking,
|
Linking,
|
||||||
NativeModules,
|
NativeModules,
|
||||||
Text,
|
Text,
|
||||||
|
@ -10,16 +9,21 @@ import {
|
||||||
View
|
View
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { NavigationActions, StackActions } from 'react-navigation';
|
import { NavigationActions, StackActions } from 'react-navigation';
|
||||||
import Colors from '../../styles/colors';
|
import AsyncStorage from '@react-native-community/async-storage';
|
||||||
import Constants from '../../constants';
|
import Colors from 'styles/colors';
|
||||||
|
import Constants from 'constants';
|
||||||
|
import WalletPage from './internal/wallet-page';
|
||||||
import WelcomePage from './internal/welcome-page';
|
import WelcomePage from './internal/welcome-page';
|
||||||
import EmailCollectPage from './internal/email-collect-page';
|
import EmailCollectPage from './internal/email-collect-page';
|
||||||
import firstRunStyle from '../../styles/firstRun';
|
import SkipAccountPage from './internal/skip-account-page';
|
||||||
|
import firstRunStyle from 'styles/firstRun';
|
||||||
|
|
||||||
class FirstRunScreen extends React.PureComponent {
|
class FirstRunScreen extends React.PureComponent {
|
||||||
static pages = [
|
static pages = [
|
||||||
'welcome',
|
Constants.FIRST_RUN_PAGE_WELCOME,
|
||||||
'email-collect'
|
Constants.FIRST_RUN_PAGE_EMAIL_COLLECT,
|
||||||
|
Constants.FIRST_RUN_PAGE_WALLET,
|
||||||
|
Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT,
|
||||||
];
|
];
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
@ -28,7 +32,9 @@ class FirstRunScreen extends React.PureComponent {
|
||||||
isFirstRun: false,
|
isFirstRun: false,
|
||||||
launchUrl: null,
|
launchUrl: null,
|
||||||
showSkip: false,
|
showSkip: false,
|
||||||
showBottomContainer: true
|
skipAccountConfirmed: false,
|
||||||
|
showBottomContainer: true,
|
||||||
|
walletPassword: null
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -63,8 +69,8 @@ class FirstRunScreen extends React.PureComponent {
|
||||||
if (emailNewErrorMessage) {
|
if (emailNewErrorMessage) {
|
||||||
notify ({ message: String(emailNewErrorMessage), isError: true });
|
notify ({ message: String(emailNewErrorMessage), isError: true });
|
||||||
} else {
|
} else {
|
||||||
// Request successful. Navigate to discover.
|
// Request successful. Navigate to next page (wallet).
|
||||||
this.closeFinalPage();
|
this.showNextPage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,14 +86,40 @@ class FirstRunScreen extends React.PureComponent {
|
||||||
navigation.dispatch(resetAction);
|
navigation.dispatch(resetAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleLeftButtonPressed = () => {
|
||||||
|
// Go to setup account page when "Setup account" is pressed
|
||||||
|
if (Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT === this.state.currentPage) {
|
||||||
|
return this.showPage(Constants.FIRST_RUN_PAGE_EMAIL_COLLECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go to skip account page when "No, thanks" is pressed
|
||||||
|
if (Constants.FIRST_RUN_PAGE_EMAIL_COLLECT === this.state.currentPage) {
|
||||||
|
this.showPage(Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleContinuePressed = () => {
|
handleContinuePressed = () => {
|
||||||
|
const { notify } = this.props;
|
||||||
const pageIndex = FirstRunScreen.pages.indexOf(this.state.currentPage);
|
const pageIndex = FirstRunScreen.pages.indexOf(this.state.currentPage);
|
||||||
if (this.state.currentPage !== 'email-collect' &&
|
if (Constants.FIRST_RUN_PAGE_WALLET === this.state.currentPage) {
|
||||||
pageIndex === (FirstRunScreen.pages.length - 1)) {
|
if (!this.state.walletPassword || this.state.walletPassword.trim().length < 6) {
|
||||||
|
return notify({ message: 'Your wallet password should be at least 6 characters long' });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.closeFinalPage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT === this.state.currentPage && !this.state.skipAccountConfirmed) {
|
||||||
|
notify({ message: 'Please confirm that you want to use LBRY without creating an account.' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Constants.FIRST_RUN_PAGE_EMAIL_COLLECT !== this.state.currentPage && pageIndex === (FirstRunScreen.pages.length - 1)) {
|
||||||
this.closeFinalPage();
|
this.closeFinalPage();
|
||||||
} else {
|
} else {
|
||||||
// TODO: Actions and page verification for specific pages
|
// TODO: Actions and page verification for specific pages
|
||||||
if (this.state.currentPage === 'email-collect') {
|
if (Constants.FIRST_RUN_PAGE_EMAIL_COLLECT === this.state.currentPage) {
|
||||||
// handle email collect
|
// handle email collect
|
||||||
this.handleEmailCollectPageContinue();
|
this.handleEmailCollectPageContinue();
|
||||||
} else {
|
} else {
|
||||||
|
@ -98,21 +130,10 @@ class FirstRunScreen extends React.PureComponent {
|
||||||
|
|
||||||
handleEmailCollectPageContinue() {
|
handleEmailCollectPageContinue() {
|
||||||
const { notify, addUserEmail } = this.props;
|
const { notify, addUserEmail } = this.props;
|
||||||
const pageIndex = FirstRunScreen.pages.indexOf(this.state.currentPage);
|
|
||||||
|
|
||||||
AsyncStorage.getItem(Constants.KEY_FIRST_RUN_EMAIL).then(email => {
|
AsyncStorage.getItem(Constants.KEY_FIRST_RUN_EMAIL).then(email => {
|
||||||
if (!email || email.trim().length === 0) {
|
|
||||||
// no email provided. Skip.
|
|
||||||
if (this.state.currentPage === 'email-collect' && pageIndex === (FirstRunScreen.pages.length - 1)) {
|
|
||||||
this.closeFinalPage();
|
|
||||||
} else {
|
|
||||||
this.showNextPage();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate the email
|
// validate the email
|
||||||
if (email.indexOf('@') === -1) {
|
if (!email || email.indexOf('@') === -1) {
|
||||||
return notify({
|
return notify({
|
||||||
message: 'Please provide a valid email address to continue.',
|
message: 'Please provide a valid email address to continue.',
|
||||||
});
|
});
|
||||||
|
@ -133,6 +154,13 @@ class FirstRunScreen extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showPage(pageName) {
|
||||||
|
const pageIndex = FirstRunScreen.pages.indexOf(pageName);
|
||||||
|
if (pageIndex > -1) {
|
||||||
|
this.setState({ currentPage: pageName });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
closeFinalPage() {
|
closeFinalPage() {
|
||||||
// Final page. Let the app know that first run experience is completed.
|
// Final page. Let the app know that first run experience is completed.
|
||||||
if (NativeModules.FirstRun) {
|
if (NativeModules.FirstRun) {
|
||||||
|
@ -144,7 +172,7 @@ class FirstRunScreen extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
onEmailChanged = (email) => {
|
onEmailChanged = (email) => {
|
||||||
if ('email-collect' == this.state.currentPage) {
|
if (Constants.FIRST_RUN_PAGE_EMAIL_COLLECT == this.state.currentPage) {
|
||||||
this.setState({ showSkip: (!email || email.trim().length === 0) });
|
this.setState({ showSkip: (!email || email.trim().length === 0) });
|
||||||
} else {
|
} else {
|
||||||
this.setState({ showSkip: false });
|
this.setState({ showSkip: false });
|
||||||
|
@ -158,6 +186,18 @@ class FirstRunScreen extends React.PureComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onWalletPasswordChanged = (password) => {
|
||||||
|
this.setState({ walletPassword: password });
|
||||||
|
}
|
||||||
|
|
||||||
|
onWalletViewLayout = () => {
|
||||||
|
this.setState({ showBottomContainer: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
onSkipSwitchChanged = (checked) => {
|
||||||
|
this.setState({ skipAccountConfirmed: checked });
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
authenticate,
|
authenticate,
|
||||||
|
@ -169,15 +209,31 @@ class FirstRunScreen extends React.PureComponent {
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
let page = null;
|
let page = null;
|
||||||
if (this.state.currentPage === 'welcome') {
|
switch (this.state.currentPage) {
|
||||||
// show welcome page
|
case 'welcome':
|
||||||
page = (<WelcomePage />);
|
page = (<WelcomePage />);
|
||||||
} else if (this.state.currentPage === 'email-collect') {
|
break;
|
||||||
page = (<EmailCollectPage authenticating={authenticating}
|
|
||||||
|
case 'email-collect':
|
||||||
|
|||||||
|
page = (<EmailCollectPage
|
||||||
|
authenticating={authenticating}
|
||||||
authToken={authToken}
|
authToken={authToken}
|
||||||
authenticate={authenticate}
|
authenticate={authenticate}
|
||||||
onEmailChanged={this.onEmailChanged}
|
onEmailChanged={this.onEmailChanged}
|
||||||
onEmailViewLayout={this.onEmailViewLayout} />);
|
onEmailViewLayout={this.onEmailViewLayout} />);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'wallet':
|
||||||
|
page = (<WalletPage
|
||||||
|
onWalletViewLayout={this.onWalletViewLayout}
|
||||||
|
onPasswordChanged={this.onWalletPasswordChanged} />);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'skip-account':
|
||||||
|
page = (<SkipAccountPage
|
||||||
|
onSkipAccountViewLayout={this.onSkipAccountViewLayout}
|
||||||
|
onSkipSwitchChanged={this.onSkipSwitchChanged} />);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -188,10 +244,25 @@ class FirstRunScreen extends React.PureComponent {
|
||||||
{emailNewPending &&
|
{emailNewPending &&
|
||||||
<ActivityIndicator size="small" color={Colors.White} style={firstRunStyle.pageWaiting} />}
|
<ActivityIndicator size="small" color={Colors.White} style={firstRunStyle.pageWaiting} />}
|
||||||
|
|
||||||
|
<View style={firstRunStyle.buttonRow}>
|
||||||
|
{([Constants.FIRST_RUN_PAGE_WELCOME, Constants.FIRST_RUN_PAGE_WALLET].indexOf(this.state.currentPage) > -1) && <View />}
|
||||||
|
{Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT === this.state.currentPage &&
|
||||||
|
<TouchableOpacity style={firstRunStyle.leftButton} onPress={this.handleLeftButtonPressed}>
|
||||||
|
<Text style={firstRunStyle.buttonText}>« Setup account</Text>
|
||||||
|
</TouchableOpacity>}
|
||||||
|
{!emailNewPending && (Constants.FIRST_RUN_PAGE_EMAIL_COLLECT === this.state.currentPage) &&
|
||||||
|
<TouchableOpacity style={firstRunStyle.leftButton} onPress={this.handleLeftButtonPressed}>
|
||||||
|
<Text style={firstRunStyle.smallLeftButtonText}>No, thanks »</Text>
|
||||||
|
</TouchableOpacity>}
|
||||||
|
|
||||||
{!emailNewPending &&
|
{!emailNewPending &&
|
||||||
<TouchableOpacity style={firstRunStyle.button} onPress={this.handleContinuePressed}>
|
<TouchableOpacity style={firstRunStyle.button} onPress={this.handleContinuePressed}>
|
||||||
<Text style={firstRunStyle.buttonText}>{this.state.showSkip ? 'Skip': 'Continue'}</Text>
|
{Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT === this.state.currentPage &&
|
||||||
|
<Text style={firstRunStyle.smallButtonText}>Use LBRY »</Text>}
|
||||||
|
{Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT !== this.state.currentPage &&
|
||||||
|
<Text style={firstRunStyle.buttonText}>{Constants.FIRST_RUN_PAGE_WALLET === this.state.currentPage ? 'Use LBRY' : 'Continue'} »</Text>}
|
||||||
</TouchableOpacity>}
|
</TouchableOpacity>}
|
||||||
|
</View>
|
||||||
</View>}
|
</View>}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
|
@ -53,9 +53,18 @@ class SearchPage extends React.PureComponent {
|
||||||
<UriBar value={searchQuery}
|
<UriBar value={searchQuery}
|
||||||
navigation={navigation}
|
navigation={navigation}
|
||||||
onSearchSubmitted={this.handleSearchSubmitted} />
|
onSearchSubmitted={this.handleSearchSubmitted} />
|
||||||
|
{isSearching &&
|
||||||
|
<View style={searchStyle.busyContainer}>
|
||||||
|
<ActivityIndicator size="large" color={Colors.LbryGreen} style={searchStyle.loading} />
|
||||||
|
</View>}
|
||||||
|
|
||||||
{!isSearching && (!uris || uris.length === 0) &&
|
{!isSearching && (!uris || uris.length === 0) &&
|
||||||
<Text style={searchStyle.noResultsText}>No results to display.</Text>}
|
<Text style={searchStyle.noResultsText}>No results to display.</Text>}
|
||||||
<ScrollView style={searchStyle.scrollContainer} contentContainerStyle={searchStyle.scrollPadding}>
|
{!isSearching &&
|
||||||
|
<ScrollView
|
||||||
|
style={searchStyle.scrollContainer}
|
||||||
|
contentContainerStyle={searchStyle.scrollPadding}
|
||||||
Duplicated conditional, use fragments? Duplicated conditional, use fragments?
|
|||||||
|
keyboardShouldPersistTaps={'handled'}>
|
||||||
{this.state.currentUri &&
|
{this.state.currentUri &&
|
||||||
<FileListItem
|
<FileListItem
|
||||||
key={this.state.currentUri}
|
key={this.state.currentUri}
|
||||||
|
@ -65,15 +74,14 @@ class SearchPage extends React.PureComponent {
|
||||||
navigation={navigation}
|
navigation={navigation}
|
||||||
onPress={() => navigateToUri(navigation, this.state.currentUri)}
|
onPress={() => navigateToUri(navigation, this.state.currentUri)}
|
||||||
/>}
|
/>}
|
||||||
{!isSearching && uris && uris.length ? (
|
{(uris && uris.length) ? (
|
||||||
uris.map(uri => <FileListItem key={uri}
|
uris.map(uri => <FileListItem key={uri}
|
||||||
uri={uri}
|
uri={uri}
|
||||||
style={searchStyle.resultItem}
|
style={searchStyle.resultItem}
|
||||||
navigation={navigation}
|
navigation={navigation}
|
||||||
onPress={() => navigateToUri(navigation, uri)}/>)
|
onPress={() => navigateToUri(navigation, uri)}/>)
|
||||||
) : null }
|
) : null }
|
||||||
</ScrollView>
|
</ScrollView>}
|
||||||
{isSearching && <ActivityIndicator size="large" color={Colors.LbryGreen} style={searchStyle.loading} /> }
|
|
||||||
<FloatingWalletBalance navigation={navigation} />
|
<FloatingWalletBalance navigation={navigation} />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doBalanceSubscribe, doUpdateBlockHeight, doToast } from 'lbry-redux';
|
import { doTotalBalanceSubscribe, doUpdateBlockHeight, doToast } from 'lbry-redux';
|
||||||
import {
|
import {
|
||||||
doAuthenticate,
|
doAuthenticate,
|
||||||
doBlackListedOutpointsSubscribe,
|
doBlackListedOutpointsSubscribe,
|
||||||
doCheckSubscriptionsInit,
|
doCheckSubscriptionsInit,
|
||||||
doFetchMySubscriptions,
|
doFetchMySubscriptions,
|
||||||
doFetchRewardedContent,
|
doFetchRewardedContent,
|
||||||
|
doGetSync,
|
||||||
doUserEmailToVerify,
|
doUserEmailToVerify,
|
||||||
doUserEmailVerify,
|
doUserEmailVerify,
|
||||||
doUserEmailVerifyFailure,
|
doUserEmailVerifyFailure,
|
||||||
|
@ -22,12 +23,13 @@ const select = state => ({
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
authenticate: (appVersion, os) => dispatch(doAuthenticate(appVersion, os)),
|
authenticate: (appVersion, os) => dispatch(doAuthenticate(appVersion, os)),
|
||||||
balanceSubscribe: () => dispatch(doBalanceSubscribe()),
|
totalBalanceSubscribe: () => dispatch(doTotalBalanceSubscribe()),
|
||||||
blacklistedOutpointsSubscribe: () => dispatch(doBlackListedOutpointsSubscribe()),
|
blacklistedOutpointsSubscribe: () => dispatch(doBlackListedOutpointsSubscribe()),
|
||||||
checkSubscriptionsInit: () => dispatch(doCheckSubscriptionsInit()),
|
checkSubscriptionsInit: () => dispatch(doCheckSubscriptionsInit()),
|
||||||
deleteCompleteBlobs: () => dispatch(doDeleteCompleteBlobs()),
|
deleteCompleteBlobs: () => dispatch(doDeleteCompleteBlobs()),
|
||||||
fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
|
fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
|
||||||
fetchSubscriptions: (callback) => dispatch(doFetchMySubscriptions(callback)),
|
fetchSubscriptions: (callback) => dispatch(doFetchMySubscriptions(callback)),
|
||||||
|
getSync: password => dispatch(doGetSync(password)),
|
||||||
notify: data => dispatch(doToast(data)),
|
notify: data => dispatch(doToast(data)),
|
||||||
setEmailToVerify: email => dispatch(doUserEmailToVerify(email)),
|
setEmailToVerify: email => dispatch(doUserEmailToVerify(email)),
|
||||||
updateBlockHeight: () => dispatch(doUpdateBlockHeight()),
|
updateBlockHeight: () => dispatch(doUpdateBlockHeight()),
|
||||||
|
|
|
@ -2,7 +2,6 @@ import React from 'react';
|
||||||
import { Lbry } from 'lbry-redux';
|
import { Lbry } from 'lbry-redux';
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
AsyncStorage,
|
|
||||||
Linking,
|
Linking,
|
||||||
NativeModules,
|
NativeModules,
|
||||||
Platform,
|
Platform,
|
||||||
|
@ -12,11 +11,12 @@ import {
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { NavigationActions, StackActions } from 'react-navigation';
|
import { NavigationActions, StackActions } from 'react-navigation';
|
||||||
import { decode as atob } from 'base-64';
|
import { decode as atob } from 'base-64';
|
||||||
import { navigateToUri } from '../../utils/helper';
|
import { navigateToUri } from 'utils/helper';
|
||||||
|
import AsyncStorage from '@react-native-community/async-storage';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Colors from '../../styles/colors';
|
import Colors from 'styles/colors';
|
||||||
import Constants from '../../constants';
|
import Constants from 'constants';
|
||||||
import splashStyle from '../../styles/splash';
|
import splashStyle from 'styles/splash';
|
||||||
|
|
||||||
const BLOCK_HEIGHT_INTERVAL = 1000 * 60 * 2.5; // every 2.5 minutes
|
const BLOCK_HEIGHT_INTERVAL = 1000 * 60 * 2.5; // every 2.5 minutes
|
||||||
|
|
||||||
|
@ -63,24 +63,8 @@ class SplashScreen extends React.PureComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
navigateToMain = () => {
|
||||||
const {
|
const { navigation } = this.props;
|
||||||
emailToVerify,
|
|
||||||
navigation,
|
|
||||||
setEmailToVerify,
|
|
||||||
verifyUserEmail,
|
|
||||||
verifyUserEmailFailure
|
|
||||||
} = this.props;
|
|
||||||
const { user } = nextProps;
|
|
||||||
|
|
||||||
if (this.state.daemonReady && this.state.shouldAuthenticate && user && user.id) {
|
|
||||||
this.setState({ shouldAuthenticate: false }, () => {
|
|
||||||
AsyncStorage.getItem(Constants.KEY_FIRST_RUN_EMAIL).then(email => {
|
|
||||||
if (email) {
|
|
||||||
setEmailToVerify(email);
|
|
||||||
}
|
|
||||||
|
|
||||||
// user is authenticated, navigate to the main view
|
|
||||||
const resetAction = StackActions.reset({
|
const resetAction = StackActions.reset({
|
||||||
index: 0,
|
index: 0,
|
||||||
actions: [
|
actions: [
|
||||||
|
@ -116,11 +100,65 @@ class SplashScreen extends React.PureComponent {
|
||||||
navigateToUri(navigation, launchUrl);
|
navigateToUri(navigation, launchUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
const {
|
||||||
|
emailToVerify,
|
||||||
|
getSync,
|
||||||
|
setEmailToVerify,
|
||||||
|
verifyUserEmail,
|
||||||
|
verifyUserEmailFailure
|
||||||
|
} = this.props;
|
||||||
|
const { user } = nextProps;
|
||||||
|
|
||||||
|
if (this.state.daemonReady && this.state.shouldAuthenticate && user && user.id) {
|
||||||
|
this.setState({ shouldAuthenticate: false }, () => {
|
||||||
|
AsyncStorage.getItem(Constants.KEY_FIRST_RUN_EMAIL).then(email => {
|
||||||
|
if (email) {
|
||||||
|
setEmailToVerify(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
// user is authenticated, navigate to the main view
|
||||||
|
if (user.has_verified_email) {
|
||||||
|
NativeModules.UtilityModule.getSecureValue(Constants.KEY_FIRST_RUN_PASSWORD).then(walletPassword => {
|
||||||
|
getSync(walletPassword);
|
||||||
|
this.navigateToMain();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.navigateToMain();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finishSplashScreen = () => {
|
||||||
|
const {
|
||||||
|
authenticate,
|
||||||
|
totalBalanceSubscribe,
|
||||||
|
blacklistedOutpointsSubscribe,
|
||||||
|
checkSubscriptionsInit,
|
||||||
|
updateBlockHeight,
|
||||||
|
navigation,
|
||||||
|
notify
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
Lbry.resolve({ urls: 'lbry://one' }).then(() => {
|
||||||
|
// Leave the splash screen
|
||||||
|
totalBalanceSubscribe();
|
||||||
|
blacklistedOutpointsSubscribe();
|
||||||
|
checkSubscriptionsInit();
|
||||||
|
updateBlockHeight();
|
||||||
|
setInterval(() => { updateBlockHeight(); }, BLOCK_HEIGHT_INTERVAL);
|
||||||
|
NativeModules.VersionInfo.getAppVersion().then(appVersion => {
|
||||||
|
this.setState({ shouldAuthenticate: true });
|
||||||
|
authenticate(appVersion, Platform.OS);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_updateStatusCallback(status) {
|
_updateStatusCallback(status) {
|
||||||
const { deleteCompleteBlobs, fetchSubscriptions } = this.props;
|
const { deleteCompleteBlobs, fetchSubscriptions } = this.props;
|
||||||
const startupStatus = status.startup_status;
|
const startupStatus = status.startup_status;
|
||||||
|
@ -141,33 +179,40 @@ class SplashScreen extends React.PureComponent {
|
||||||
isRunning: true,
|
isRunning: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// fetch subscriptions, so that we can check for new content after resolve
|
AsyncStorage.getItem(Constants.KEY_FIRST_RUN_PASSWORD).then(passwordSet => {
|
||||||
Lbry.resolve({ urls: 'lbry://one' }).then(() => {
|
if ("true" === passwordSet) {
|
||||||
// Leave the splash screen
|
// encrypt the wallet
|
||||||
const {
|
NativeModules.UtilityModule.getSecureValue(Constants.KEY_FIRST_RUN_PASSWORD).then(password => {
|
||||||
authenticate,
|
if (!password || password.trim().length === 0) {
|
||||||
balanceSubscribe,
|
this.finishSplashScreen();
|
||||||
blacklistedOutpointsSubscribe,
|
return;
|
||||||
checkSubscriptionsInit,
|
}
|
||||||
updateBlockHeight,
|
|
||||||
navigation,
|
|
||||||
notify
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
balanceSubscribe();
|
Lbry.account_encrypt({ new_password: password }).then((result) => {
|
||||||
blacklistedOutpointsSubscribe();
|
AsyncStorage.removeItem(Constants.KEY_FIRST_RUN_PASSWORD);
|
||||||
checkSubscriptionsInit();
|
Lbry.account_unlock({ password }).then(() => this.finishSplashScreen());
|
||||||
updateBlockHeight();
|
|
||||||
setInterval(() => { updateBlockHeight(); }, BLOCK_HEIGHT_INTERVAL);
|
|
||||||
NativeModules.VersionInfo.getAppVersion().then(appVersion => {
|
|
||||||
this.setState({ shouldAuthenticate: true });
|
|
||||||
authenticate(appVersion, Platform.OS);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For now, automatically unlock the wallet if a password is set so that downloads work
|
||||||
|
NativeModules.UtilityModule.getSecureValue(Constants.KEY_FIRST_RUN_PASSWORD).then(password => {
|
||||||
|
if (password && password.trim().length > 0) {
|
||||||
|
// unlock the wallet and then finish the splash screen
|
||||||
|
Lbry.account_unlock({ password }).then(() => this.finishSplashScreen());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.finishSplashScreen();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const blockchainHeaders = status.blockchain_headers;
|
const blockchainHeaders = status.blockchain_headers;
|
||||||
const walletStatus = status.wallet;
|
const walletStatus = status.wallet;
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ import React from 'react';
|
||||||
import NavigationActions from 'react-navigation';
|
import NavigationActions from 'react-navigation';
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
AsyncStorage,
|
|
||||||
FlatList,
|
FlatList,
|
||||||
NativeModules,
|
NativeModules,
|
||||||
SectionList,
|
SectionList,
|
||||||
|
@ -12,6 +11,7 @@ import {
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { buildURI, parseURI } from 'lbry-redux';
|
import { buildURI, parseURI } from 'lbry-redux';
|
||||||
import { uriFromFileInfo } from 'utils/helper';
|
import { uriFromFileInfo } from 'utils/helper';
|
||||||
|
import AsyncStorage from '@react-native-community/async-storage';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import Colors from 'styles/colors';
|
import Colors from 'styles/colors';
|
||||||
|
|
|
@ -2,13 +2,13 @@ import React from 'react';
|
||||||
import NavigationActions from 'react-navigation';
|
import NavigationActions from 'react-navigation';
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
AsyncStorage,
|
|
||||||
NativeModules,
|
NativeModules,
|
||||||
FlatList,
|
FlatList,
|
||||||
Text,
|
Text,
|
||||||
View
|
View
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { normalizeURI } from 'lbry-redux';
|
import { normalizeURI } from 'lbry-redux';
|
||||||
|
import AsyncStorage from '@react-native-community/async-storage';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import FileItem from '/component/fileItem';
|
import FileItem from '/component/fileItem';
|
||||||
import discoverStyle from 'styles/discover';
|
import discoverStyle from 'styles/discover';
|
||||||
|
|
|
@ -2,14 +2,17 @@ import { connect } from 'react-redux';
|
||||||
import { doSetClientSetting } from 'redux/actions/settings';
|
import { doSetClientSetting } from 'redux/actions/settings';
|
||||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import { doPushDrawerStack } from 'redux/actions/drawer';
|
import { doPushDrawerStack } from 'redux/actions/drawer';
|
||||||
|
import { doGetSync, selectUser } from 'lbryinc';
|
||||||
import Constants from 'constants';
|
import Constants from 'constants';
|
||||||
import WalletPage from './view';
|
import WalletPage from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
|
user: selectUser(state),
|
||||||
understandsRisks: makeSelectClientSetting(Constants.SETTING_ALPHA_UNDERSTANDS_RISKS)(state),
|
understandsRisks: makeSelectClientSetting(Constants.SETTING_ALPHA_UNDERSTANDS_RISKS)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
|
getSync: password => dispatch(doGetSync(password)),
|
||||||
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
||||||
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_WALLET))
|
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_WALLET))
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ScrollView, Text, View } from 'react-native';
|
import { NativeModules, ScrollView, Text, View } from 'react-native';
|
||||||
import TransactionListRecent from 'component/transactionListRecent';
|
import TransactionListRecent from 'component/transactionListRecent';
|
||||||
import WalletAddress from 'component/walletAddress';
|
import WalletAddress from 'component/walletAddress';
|
||||||
import WalletBalance from 'component/walletBalance';
|
import WalletBalance from 'component/walletBalance';
|
||||||
|
@ -13,6 +13,11 @@ import walletStyle from 'styles/wallet';
|
||||||
class WalletPage extends React.PureComponent {
|
class WalletPage extends React.PureComponent {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.pushDrawerStack();
|
this.props.pushDrawerStack();
|
||||||
|
|
||||||
|
const { user, getSync } = this.props;
|
||||||
|
if (user && user.has_verified_email) {
|
||||||
|
NativeModules.UtilityModule.getSecureValue(Constants.KEY_FIRST_RUN_PASSWORD).then(walletPassword => getSync(walletPassword));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -246,11 +246,8 @@ const filePageStyle = StyleSheet.create({
|
||||||
color: "rgba(64, 184, 154, .2)"
|
color: "rgba(64, 184, 154, .2)"
|
||||||
},
|
},
|
||||||
tipCard: {
|
tipCard: {
|
||||||
backgroundColor: Colors.White,
|
|
||||||
position: 'absolute',
|
|
||||||
top: containedMediaHeightWithControls - 16,
|
|
||||||
width: '100%',
|
width: '100%',
|
||||||
paddingTop: 8,
|
marginTop: -12,
|
||||||
paddingBottom: 8,
|
paddingBottom: 8,
|
||||||
paddingLeft: 16,
|
paddingLeft: 16,
|
||||||
paddingRight: 16
|
paddingRight: 16
|
||||||
|
@ -298,6 +295,9 @@ const filePageStyle = StyleSheet.create({
|
||||||
fontFamily: 'Inter-UI-Regular',
|
fontFamily: 'Inter-UI-Regular',
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
lineHeight: 24
|
lineHeight: 24
|
||||||
|
},
|
||||||
|
tipButton: {
|
||||||
|
marginRight: 8
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,11 @@ import { StyleSheet } from 'react-native';
|
||||||
import Colors from './colors';
|
import Colors from './colors';
|
||||||
|
|
||||||
const firstRunStyle = StyleSheet.create({
|
const firstRunStyle = StyleSheet.create({
|
||||||
|
row: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginLeft: 32,
|
||||||
|
marginRight: 32,
|
||||||
|
},
|
||||||
screenContainer: {
|
screenContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: Colors.LbryGreen
|
backgroundColor: Colors.LbryGreen
|
||||||
|
@ -37,6 +42,21 @@ const firstRunStyle = StyleSheet.create({
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
color: Colors.White
|
color: Colors.White
|
||||||
},
|
},
|
||||||
|
confirmContainer: {
|
||||||
|
marginTop: 36
|
||||||
|
},
|
||||||
|
rowParagraph: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 16,
|
||||||
|
lineHeight: 24,
|
||||||
|
color: Colors.White,
|
||||||
|
flex: 0.7
|
||||||
|
},
|
||||||
|
rowSwitch: {
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
flex: 0.2,
|
||||||
|
marginRight: 8
|
||||||
|
},
|
||||||
emailInput: {
|
emailInput: {
|
||||||
fontFamily: 'Inter-UI-Regular',
|
fontFamily: 'Inter-UI-Regular',
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
|
@ -46,15 +66,30 @@ const firstRunStyle = StyleSheet.create({
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
textAlign: 'center'
|
textAlign: 'center'
|
||||||
},
|
},
|
||||||
|
passwordInput: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 24,
|
||||||
|
lineHeight: 24,
|
||||||
|
marginLeft: 32,
|
||||||
|
marginRight: 32,
|
||||||
|
marginBottom: 20,
|
||||||
|
textAlign: 'center'
|
||||||
|
},
|
||||||
leftButton: {
|
leftButton: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
alignSelf: 'flex-start',
|
alignSelf: 'flex-end',
|
||||||
|
paddingBottom: 16,
|
||||||
marginLeft: 32,
|
marginLeft: 32,
|
||||||
marginRight: 32
|
marginRight: 32
|
||||||
},
|
},
|
||||||
bottomContainer: {
|
bottomContainer: {
|
||||||
flex: 1
|
flex: 1
|
||||||
},
|
},
|
||||||
|
buttonRow: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between'
|
||||||
|
},
|
||||||
actionButton: {
|
actionButton: {
|
||||||
backgroundColor: Colors.White,
|
backgroundColor: Colors.White,
|
||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
|
@ -71,14 +106,29 @@ const firstRunStyle = StyleSheet.create({
|
||||||
},
|
},
|
||||||
buttonText: {
|
buttonText: {
|
||||||
fontFamily: 'Inter-UI-Regular',
|
fontFamily: 'Inter-UI-Regular',
|
||||||
fontSize: 24,
|
fontSize: 18,
|
||||||
color: Colors.White
|
color: Colors.White
|
||||||
},
|
},
|
||||||
|
smallButtonText: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.White,
|
||||||
|
marginBottom: -2
|
||||||
|
},
|
||||||
|
smallLeftButtonText: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.White,
|
||||||
|
marginBottom: 6
|
||||||
|
},
|
||||||
waiting: {
|
waiting: {
|
||||||
marginBottom: 24
|
marginBottom: 24
|
||||||
},
|
},
|
||||||
pageWaiting: {
|
pageWaiting: {
|
||||||
alignSelf: 'center'
|
alignSelf: 'center'
|
||||||
|
},
|
||||||
|
titleIcon: {
|
||||||
|
marginTop: 8
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -30,9 +30,9 @@ const floatingButtonStyle = StyleSheet.create({
|
||||||
pendingContainer: {
|
pendingContainer: {
|
||||||
borderRadius: 24,
|
borderRadius: 24,
|
||||||
padding: 14,
|
padding: 14,
|
||||||
paddingLeft: 70,
|
paddingLeft: 20,
|
||||||
paddingRight: 20,
|
paddingRight: 70,
|
||||||
marginLeft: -60,
|
marginRight: -60,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
backgroundColor: Colors.BrighterLbryGreen,
|
backgroundColor: Colors.BrighterLbryGreen,
|
||||||
|
|
|
@ -4,15 +4,16 @@ import Colors from 'styles/colors';
|
||||||
const searchStyle = StyleSheet.create({
|
const searchStyle = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center'
|
|
||||||
},
|
},
|
||||||
scrollContainer: {
|
scrollContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
marginTop: 60
|
marginTop: 60
|
||||||
},
|
},
|
||||||
|
busyContainer: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
scrollPadding: {
|
scrollPadding: {
|
||||||
paddingBottom: 16
|
paddingBottom: 16
|
||||||
},
|
},
|
||||||
|
|
|
@ -36,7 +36,7 @@ version.filename = %(source.dir)s/main.py
|
||||||
|
|
||||||
# (list) Application requirements
|
# (list) Application requirements
|
||||||
# comma seperated e.g. requirements = sqlite3,kivy
|
# comma seperated e.g. requirements = sqlite3,kivy
|
||||||
requirements = python3crystax, openssl, sqlite3, hostpython3crystax, android, distro, pyjnius, certifi==2018.4.16, constantly, incremental, miniupnpc==1.9, gmpy, appdirs==1.4.3, argparse==1.2.1, docopt, base58==1.0.0, colorama==0.3.7, dnspython==1.12.0, ecdsa==0.13, envparse, jsonrpclib==0.1.7, jsonschema==2.5.1, pbkdf2, pyyaml, qrcode==5.2.2, requests, seccure==0.3.1.3, attrs==18.1.0, pyasn1, pyasn1-modules, service_identity==16.0.0, six==1.9.0, txJSON-RPC, zope.interface==4.3.3, protobuf==3.6.1, keyring==10.4.0, txupnp, git+https://github.com/lbryio/lbryschema.git#egg=lbryschema, git+https://github.com/lbryio/lbry.git@v0.32.4#egg=lbrynet, git+https://github.com/lbryio/aioupnp.git#egg=aioupnp, asn1crypto, treq==17.8.0, funcsigs, mock, pbr, pyopenssl, twisted, idna, Automat, hyperlink, PyHamcrest, netifaces, cryptography, aiohttp==3.5.4, multidict==4.5.2, idna_ssl==1.1.0, typing_extensions==3.6.5, yarl, chardet==3.0.4, async_timeout==3.0.1, aiorpcX==0.9.0, git+https://github.com/lbryio/torba#egg=torba, coincurve
|
requirements = python3crystax, openssl, sqlite3, hostpython3crystax, android, distro==1.4.0, pyjnius, certifi==2018.11.29, appdirs==1.4.3, docopt==0.6.2, base58==1.0.0, colorama==0.3.7, ecdsa==0.13, jsonschema==2.6.0, pbkdf2==1.3, pyyaml, protobuf==3.6.1, keyring==10.4.0, git+https://github.com/lbryio/lbry.git@v0.34.0#egg=lbrynet, git+https://github.com/lbryio/aioupnp.git#egg=aioupnp, asn1crypto, mock, netifaces, cryptography, aiohttp==3.5.4, multidict==4.5.2, yarl==1.3.0, chardet==3.0.4, async_timeout==3.0.1, git+https://github.com/lbryio/torba#egg=torba, coincurve, msgpack==0.6.1, six, attrs==18.2.0, pylru
|
||||||
|
|
||||||
# (str) Custom source folders for requirements
|
# (str) Custom source folders for requirements
|
||||||
# Sets custom source for any requirements with recipes
|
# Sets custom source for any requirements with recipes
|
||||||
|
@ -89,7 +89,7 @@ fullscreen = 0
|
||||||
android.permissions = ACCESS_NETWORK_STATE,BLUETOOTH,INTERNET,READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE
|
android.permissions = ACCESS_NETWORK_STATE,BLUETOOTH,INTERNET,READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE
|
||||||
|
|
||||||
# (int) Android API to use
|
# (int) Android API to use
|
||||||
android.api = 27
|
android.api = 28
|
||||||
|
|
||||||
# (int) Minimum API required
|
# (int) Minimum API required
|
||||||
android.minapi = 21
|
android.minapi = 21
|
||||||
|
@ -148,7 +148,7 @@ android.react_src = ./app
|
||||||
|
|
||||||
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
|
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
|
||||||
# bootstrap)
|
# bootstrap)
|
||||||
android.gradle_dependencies = com.android.support:support-v4:27.1.1, com.android.support:support-media-compat:27.1.1, com.android.support:appcompat-v7:27.1.1, com.facebook.react:react-native:0.55.3, com.mixpanel.android:mixpanel-android:5+, com.google.android.gms:play-services-gcm:11.0.4+, com.facebook.fresco:fresco:1.9.0, com.facebook.fresco:animated-gif:1.9.0, com.squareup.picasso:picasso:2.71828
|
android.gradle_dependencies = com.android.support:support-v4:27.1.1, com.android.support:support-media-compat:27.1.1, com.android.support:appcompat-v7:27.1.1, com.facebook.react:react-native:0.59.3, com.mixpanel.android:mixpanel-android:5+, com.google.android.gms:play-services-gcm:11.0.4+, com.facebook.fresco:fresco:1.9.0, com.facebook.fresco:animated-gif:1.9.0, com.squareup.picasso:picasso:2.71828
|
||||||
|
|
||||||
# (str) python-for-android branch to use, defaults to master
|
# (str) python-for-android branch to use, defaults to master
|
||||||
#p4a.branch = stable
|
#p4a.branch = stable
|
||||||
|
|
|
@ -36,7 +36,7 @@ version.filename = %(source.dir)s/main.py
|
||||||
|
|
||||||
# (list) Application requirements
|
# (list) Application requirements
|
||||||
# comma seperated e.g. requirements = sqlite3,kivy
|
# comma seperated e.g. requirements = sqlite3,kivy
|
||||||
requirements = python3crystax, openssl, sqlite3, hostpython3crystax, android, distro, pyjnius, certifi==2018.4.16, constantly, incremental, miniupnpc==1.9, gmpy, appdirs==1.4.3, argparse==1.2.1, docopt, base58==1.0.0, colorama==0.3.7, dnspython==1.12.0, ecdsa==0.13, envparse, jsonrpclib==0.1.7, jsonschema==2.5.1, pbkdf2, pyyaml, qrcode==5.2.2, requests, seccure==0.3.1.3, attrs==18.1.0, pyasn1, pyasn1-modules, service_identity==16.0.0, six==1.9.0, txJSON-RPC, zope.interface==4.3.3, protobuf==3.6.1, keyring==10.4.0, txupnp, git+https://github.com/lbryio/lbryschema.git#egg=lbryschema, git+https://github.com/lbryio/lbry.git@v0.32.4#egg=lbrynet, git+https://github.com/lbryio/aioupnp.git#egg=aioupnp, asn1crypto, treq==17.8.0, funcsigs, mock, pbr, pyopenssl, twisted, idna, Automat, hyperlink, PyHamcrest, netifaces, cryptography, aiohttp==3.5.4, multidict==4.5.2, idna_ssl==1.1.0, typing_extensions==3.6.5, yarl, chardet==3.0.4, async_timeout==3.0.1, aiorpcX==0.9.0, git+https://github.com/lbryio/torba#egg=torba, coincurve
|
requirements = python3crystax, openssl, sqlite3, hostpython3crystax, android, distro==1.4.0, pyjnius, certifi==2018.11.29, appdirs==1.4.3, docopt==0.6.2, base58==1.0.0, colorama==0.3.7, ecdsa==0.13, jsonschema==2.6.0, pbkdf2==1.3, pyyaml, protobuf==3.6.1, keyring==10.4.0, git+https://github.com/lbryio/lbry.git@v0.34.0#egg=lbrynet, git+https://github.com/lbryio/aioupnp.git#egg=aioupnp, asn1crypto, mock, netifaces, cryptography, aiohttp==3.5.4, multidict==4.5.2, yarl==1.3.0, chardet==3.0.4, async_timeout==3.0.1, git+https://github.com/lbryio/torba#egg=torba, coincurve, msgpack==0.6.1, six, attrs==18.2.0, pylru
|
||||||
|
|
||||||
# (str) Custom source folders for requirements
|
# (str) Custom source folders for requirements
|
||||||
# Sets custom source for any requirements with recipes
|
# Sets custom source for any requirements with recipes
|
||||||
|
@ -89,7 +89,7 @@ fullscreen = 0
|
||||||
android.permissions = ACCESS_NETWORK_STATE,BLUETOOTH,INTERNET,READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE
|
android.permissions = ACCESS_NETWORK_STATE,BLUETOOTH,INTERNET,READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE
|
||||||
|
|
||||||
# (int) Android API to use
|
# (int) Android API to use
|
||||||
android.api = 27
|
android.api = 28
|
||||||
|
|
||||||
# (int) Minimum API required
|
# (int) Minimum API required
|
||||||
android.minapi = 21
|
android.minapi = 21
|
||||||
|
@ -148,7 +148,7 @@ android.react_src = ./app
|
||||||
|
|
||||||
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
|
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
|
||||||
# bootstrap)
|
# bootstrap)
|
||||||
android.gradle_dependencies = com.android.support:support-v4:27.1.1, com.android.support:support-media-compat:27.1.1, com.android.support:appcompat-v7:27.1.1, com.facebook.react:react-native:0.55.3, com.mixpanel.android:mixpanel-android:5+, com.google.android.gms:play-services-gcm:11.0.4+, com.facebook.fresco:fresco:1.9.0, com.facebook.fresco:animated-gif:1.9.0, com.squareup.picasso:picasso:2.71828
|
android.gradle_dependencies = com.android.support:support-v4:27.1.1, com.android.support:support-media-compat:27.1.1, com.android.support:appcompat-v7:27.1.1, com.facebook.react:react-native:0.59.3, com.mixpanel.android:mixpanel-android:5+, com.google.android.gms:play-services-gcm:11.0.4+, com.facebook.fresco:fresco:1.9.0, com.facebook.fresco:animated-gif:1.9.0, com.squareup.picasso:picasso:2.71828
|
||||||
|
|
||||||
# (str) python-for-android branch to use, defaults to master
|
# (str) python-for-android branch to use, defaults to master
|
||||||
#p4a.branch = stable
|
#p4a.branch = stable
|
||||||
|
|
|
@ -36,7 +36,7 @@ version.filename = %(source.dir)s/main.py
|
||||||
|
|
||||||
# (list) Application requirements
|
# (list) Application requirements
|
||||||
# comma seperated e.g. requirements = sqlite3,kivy
|
# comma seperated e.g. requirements = sqlite3,kivy
|
||||||
requirements = python3crystax, openssl, sqlite3, hostpython3crystax, android, distro, pyjnius, certifi==2018.4.16, constantly, incremental, miniupnpc==1.9, gmpy, appdirs==1.4.3, argparse==1.2.1, docopt, base58==1.0.0, colorama==0.3.7, dnspython==1.12.0, ecdsa==0.13, envparse, jsonrpclib==0.1.7, jsonschema==2.5.1, pbkdf2, pyyaml, qrcode==5.2.2, requests, seccure==0.3.1.3, attrs==18.1.0, pyasn1, pyasn1-modules, service_identity==16.0.0, six==1.9.0, txJSON-RPC, zope.interface==4.3.3, protobuf==3.6.1, keyring==10.4.0, txupnp, git+https://github.com/lbryio/lbryschema.git#egg=lbryschema, git+https://github.com/lbryio/lbry.git@v0.32.4#egg=lbrynet, git+https://github.com/lbryio/aioupnp.git#egg=aioupnp, asn1crypto, treq==17.8.0, funcsigs, mock, pbr, pyopenssl, twisted, idna, Automat, hyperlink, PyHamcrest, netifaces, cryptography, aiohttp==3.5.4, multidict==4.5.2, idna_ssl==1.1.0, typing_extensions==3.6.5, yarl, chardet==3.0.4, async_timeout==3.0.1, aiorpcX==0.9.0, git+https://github.com/lbryio/torba#egg=torba, coincurve
|
requirements = python3crystax, openssl, sqlite3, hostpython3crystax, android, distro==1.4.0, pyjnius, certifi==2018.11.29, appdirs==1.4.3, docopt==0.6.2, base58==1.0.0, colorama==0.3.7, ecdsa==0.13, jsonschema==2.6.0, pbkdf2==1.3, pyyaml, protobuf==3.6.1, keyring==10.4.0, git+https://github.com/lbryio/lbry.git@v0.34.0#egg=lbrynet, git+https://github.com/lbryio/aioupnp.git#egg=aioupnp, asn1crypto, mock, netifaces, cryptography, aiohttp==3.5.4, multidict==4.5.2, yarl==1.3.0, chardet==3.0.4, async_timeout==3.0.1, git+https://github.com/lbryio/torba#egg=torba, coincurve, msgpack==0.6.1, six, attrs==18.2.0, pylru
|
||||||
|
|
||||||
# (str) Custom source folders for requirements
|
# (str) Custom source folders for requirements
|
||||||
# Sets custom source for any requirements with recipes
|
# Sets custom source for any requirements with recipes
|
||||||
|
@ -89,7 +89,7 @@ fullscreen = 0
|
||||||
android.permissions = ACCESS_NETWORK_STATE,BLUETOOTH,INTERNET,READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE
|
android.permissions = ACCESS_NETWORK_STATE,BLUETOOTH,INTERNET,READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE
|
||||||
|
|
||||||
# (int) Android API to use
|
# (int) Android API to use
|
||||||
android.api = 27
|
android.api = 28
|
||||||
|
|
||||||
# (int) Minimum API required
|
# (int) Minimum API required
|
||||||
android.minapi = 21
|
android.minapi = 21
|
||||||
|
@ -148,7 +148,7 @@ android.react_src = ./app
|
||||||
|
|
||||||
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
|
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
|
||||||
# bootstrap)
|
# bootstrap)
|
||||||
android.gradle_dependencies = com.android.support:support-v4:27.1.1, com.android.support:support-media-compat:27.1.1, com.android.support:appcompat-v7:27.1.1, com.facebook.react:react-native:0.55.3, com.mixpanel.android:mixpanel-android:5+, com.google.android.gms:play-services-gcm:11.0.4+, com.facebook.fresco:fresco:1.9.0, com.facebook.fresco:animated-gif:1.9.0, com.squareup.picasso:picasso:2.71828
|
android.gradle_dependencies = com.android.support:support-v4:27.1.1, com.android.support:support-media-compat:27.1.1, com.android.support:appcompat-v7:27.1.1, com.facebook.react:react-native:0.59.3, com.mixpanel.android:mixpanel-android:5+, com.google.android.gms:play-services-gcm:11.0.4+, com.facebook.fresco:fresco:1.9.0, com.facebook.fresco:animated-gif:1.9.0, com.squareup.picasso:picasso:2.71828
|
||||||
|
|
||||||
# (str) python-for-android branch to use, defaults to master
|
# (str) python-for-android branch to use, defaults to master
|
||||||
#p4a.branch = stable
|
#p4a.branch = stable
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
from os.path import (join, dirname)
|
|
||||||
from os import environ, uname
|
|
||||||
import sys
|
|
||||||
from distutils.spawn import find_executable
|
from distutils.spawn import find_executable
|
||||||
|
from os import environ
|
||||||
|
from os.path import (exists, join, dirname, split)
|
||||||
|
from glob import glob
|
||||||
|
|
||||||
from pythonforandroid.logger import warning
|
|
||||||
from pythonforandroid.recipe import Recipe
|
from pythonforandroid.recipe import Recipe
|
||||||
|
from pythonforandroid.util import BuildInterruptingException, build_platform
|
||||||
|
|
||||||
|
|
||||||
class Arch(object):
|
class Arch(object):
|
||||||
|
@ -19,6 +19,12 @@ class Arch(object):
|
||||||
super(Arch, self).__init__()
|
super(Arch, self).__init__()
|
||||||
self.ctx = ctx
|
self.ctx = ctx
|
||||||
|
|
||||||
|
# Allows injecting additional linker paths used by any recipe.
|
||||||
|
# This can also be modified by recipes (like the librt recipe)
|
||||||
|
# to make sure that some sort of global resource is available &
|
||||||
|
# linked for all others.
|
||||||
|
self.extra_global_link_paths = []
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.arch
|
return self.arch
|
||||||
|
|
||||||
|
@ -30,24 +36,65 @@ class Arch(object):
|
||||||
d.format(arch=self))
|
d.format(arch=self))
|
||||||
for d in self.ctx.include_dirs]
|
for d in self.ctx.include_dirs]
|
||||||
|
|
||||||
def get_env(self, with_flags_in_cc=True):
|
@property
|
||||||
|
def target(self):
|
||||||
|
target_data = self.command_prefix.split('-')
|
||||||
|
return '-'.join(
|
||||||
|
[target_data[0], 'none', target_data[1], target_data[2]])
|
||||||
|
|
||||||
|
def get_env(self, with_flags_in_cc=True, clang=False):
|
||||||
env = {}
|
env = {}
|
||||||
|
|
||||||
env["CFLAGS"] = " ".join([
|
cflags = [
|
||||||
"-DANDROID", "-mandroid", "-fomit-frame-pointer",
|
'-DANDROID',
|
||||||
"--sysroot", self.ctx.ndk_platform])
|
'-fomit-frame-pointer',
|
||||||
|
'-D__ANDROID_API__={}'.format(self.ctx.ndk_api)]
|
||||||
|
if not clang:
|
||||||
|
cflags.append('-mandroid')
|
||||||
|
else:
|
||||||
|
cflags.append('-target ' + self.target)
|
||||||
|
toolchain = '{android_host}-{toolchain_version}'.format(
|
||||||
|
android_host=self.ctx.toolchain_prefix,
|
||||||
|
toolchain_version=self.ctx.toolchain_version)
|
||||||
|
toolchain = join(self.ctx.ndk_dir, 'toolchains', toolchain,
|
||||||
|
'prebuilt', build_platform)
|
||||||
|
cflags.append('-gcc-toolchain {}'.format(toolchain))
|
||||||
|
|
||||||
|
env['CFLAGS'] = ' '.join(cflags)
|
||||||
|
|
||||||
|
# Link the extra global link paths first before anything else
|
||||||
|
# (such that overriding system libraries with them is possible)
|
||||||
|
env['LDFLAGS'] = ' ' + " ".join([
|
||||||
|
"-L'" + l.replace("'", "'\"'\"'") + "'" # no shlex.quote in py2
|
||||||
|
for l in self.extra_global_link_paths
|
||||||
|
]) + ' '
|
||||||
|
|
||||||
|
sysroot = join(self.ctx._ndk_dir, 'sysroot')
|
||||||
|
if exists(sysroot):
|
||||||
|
# post-15 NDK per
|
||||||
|
# https://android.googlesource.com/platform/ndk/+/ndk-r15-release/docs/UnifiedHeaders.md
|
||||||
|
env['CFLAGS'] += ' -isystem {}/sysroot/usr/include/{}'.format(
|
||||||
|
self.ctx.ndk_dir, self.ctx.toolchain_prefix)
|
||||||
|
env['CFLAGS'] += ' -I{}/sysroot/usr/include/{}'.format(
|
||||||
|
self.ctx.ndk_dir, self.command_prefix)
|
||||||
|
else:
|
||||||
|
sysroot = self.ctx.ndk_platform
|
||||||
|
env['CFLAGS'] += ' -I{}'.format(self.ctx.ndk_platform)
|
||||||
|
env['CFLAGS'] += ' -isysroot {} '.format(sysroot)
|
||||||
|
env['CFLAGS'] += '-I' + join(self.ctx.get_python_install_dir(),
|
||||||
|
'include/python{}'.format(
|
||||||
|
self.ctx.python_recipe.version[0:3])
|
||||||
|
)
|
||||||
|
|
||||||
|
env['LDFLAGS'] += '--sysroot={} '.format(self.ctx.ndk_platform)
|
||||||
|
|
||||||
env["CXXFLAGS"] = env["CFLAGS"]
|
env["CXXFLAGS"] = env["CFLAGS"]
|
||||||
|
|
||||||
env["LDFLAGS"] = " ".join(['-lm', '-L' + self.ctx.get_libs_dir(self.arch)])
|
env["LDFLAGS"] += " ".join(['-lm', '-L' + self.ctx.get_libs_dir(self.arch)])
|
||||||
|
|
||||||
if self.ctx.ndk == 'crystax':
|
if self.ctx.ndk == 'crystax':
|
||||||
env['LDFLAGS'] += ' -L{}/sources/crystax/libs/{} -lcrystax'.format(self.ctx.ndk_dir, self.arch)
|
env['LDFLAGS'] += ' -L{}/sources/crystax/libs/{} -lcrystax'.format(self.ctx.ndk_dir, self.arch)
|
||||||
|
|
||||||
py_platform = sys.platform
|
|
||||||
if py_platform in ['linux2', 'linux3']:
|
|
||||||
py_platform = 'linux'
|
|
||||||
|
|
||||||
toolchain_prefix = self.ctx.toolchain_prefix
|
toolchain_prefix = self.ctx.toolchain_prefix
|
||||||
toolchain_version = self.ctx.toolchain_version
|
toolchain_version = self.ctx.toolchain_version
|
||||||
command_prefix = self.command_prefix
|
command_prefix = self.command_prefix
|
||||||
|
@ -63,53 +110,71 @@ class Arch(object):
|
||||||
env['NDK_CCACHE'] = self.ctx.ccache
|
env['NDK_CCACHE'] = self.ctx.ccache
|
||||||
env.update({k: v for k, v in environ.items() if k.startswith('CCACHE_')})
|
env.update({k: v for k, v in environ.items() if k.startswith('CCACHE_')})
|
||||||
|
|
||||||
cc = find_executable('{command_prefix}-gcc'.format(
|
if clang:
|
||||||
command_prefix=command_prefix), path=environ['PATH'])
|
llvm_dirname = split(
|
||||||
|
glob(join(self.ctx.ndk_dir, 'toolchains', 'llvm*'))[-1])[-1]
|
||||||
|
clang_path = join(self.ctx.ndk_dir, 'toolchains', llvm_dirname,
|
||||||
|
'prebuilt', build_platform, 'bin')
|
||||||
|
environ['PATH'] = '{clang_path}:{path}'.format(
|
||||||
|
clang_path=clang_path, path=environ['PATH'])
|
||||||
|
exe = join(clang_path, 'clang')
|
||||||
|
execxx = join(clang_path, 'clang++')
|
||||||
|
else:
|
||||||
|
exe = '{command_prefix}-gcc'.format(command_prefix=command_prefix)
|
||||||
|
execxx = '{command_prefix}-g++'.format(command_prefix=command_prefix)
|
||||||
|
|
||||||
|
cc = find_executable(exe, path=environ['PATH'])
|
||||||
if cc is None:
|
if cc is None:
|
||||||
print('Searching path are: {!r}'.format(environ['PATH']))
|
print('Searching path are: {!r}'.format(environ['PATH']))
|
||||||
warning('Couldn\'t find executable for CC. This indicates a '
|
raise BuildInterruptingException(
|
||||||
|
'Couldn\'t find executable for CC. This indicates a '
|
||||||
'problem locating the {} executable in the Android '
|
'problem locating the {} executable in the Android '
|
||||||
'NDK, not that you don\'t have a normal compiler '
|
'NDK, not that you don\'t have a normal compiler '
|
||||||
'installed. Exiting.')
|
'installed. Exiting.'.format(exe))
|
||||||
exit(1)
|
|
||||||
|
|
||||||
if with_flags_in_cc:
|
if with_flags_in_cc:
|
||||||
env['CC'] = '{ccache}{command_prefix}-gcc {cflags}'.format(
|
env['CC'] = '{ccache}{exe} {cflags}'.format(
|
||||||
command_prefix=command_prefix,
|
exe=exe,
|
||||||
ccache=ccache,
|
ccache=ccache,
|
||||||
cflags=env['CFLAGS'])
|
cflags=env['CFLAGS'])
|
||||||
env['CXX'] = '{ccache}{command_prefix}-g++ {cxxflags}'.format(
|
env['CXX'] = '{ccache}{execxx} {cxxflags}'.format(
|
||||||
command_prefix=command_prefix,
|
execxx=execxx,
|
||||||
ccache=ccache,
|
ccache=ccache,
|
||||||
cxxflags=env['CXXFLAGS'])
|
cxxflags=env['CXXFLAGS'])
|
||||||
else:
|
else:
|
||||||
env['CC'] = '{ccache}{command_prefix}-gcc'.format(
|
env['CC'] = '{ccache}{exe}'.format(
|
||||||
command_prefix=command_prefix,
|
exe=exe,
|
||||||
ccache=ccache)
|
ccache=ccache)
|
||||||
env['CXX'] = '{ccache}{command_prefix}-g++'.format(
|
env['CXX'] = '{ccache}{execxx}'.format(
|
||||||
command_prefix=command_prefix,
|
execxx=execxx,
|
||||||
ccache=ccache)
|
ccache=ccache)
|
||||||
|
|
||||||
env['AR'] = '{}-ar'.format(command_prefix)
|
env['AR'] = '{}-ar'.format(command_prefix)
|
||||||
env['RANLIB'] = '{}-ranlib'.format(command_prefix)
|
env['RANLIB'] = '{}-ranlib'.format(command_prefix)
|
||||||
env['LD'] = '{}-ld'.format(command_prefix)
|
env['LD'] = '{}-ld'.format(command_prefix)
|
||||||
# env['LDSHARED'] = join(self.ctx.root_dir, 'tools', 'liblink')
|
env['LDSHARED'] = env["CC"] + " -pthread -shared " +\
|
||||||
# env['LDSHARED'] = env['LD']
|
"-Wl,-O1 -Wl,-Bsymbolic-functions "
|
||||||
|
if self.ctx.python_recipe and self.ctx.python_recipe.from_crystax:
|
||||||
|
# For crystax python, we can't use the host python headers:
|
||||||
|
env["CFLAGS"] += ' -I{}/sources/python/{}/include/python/'.\
|
||||||
|
format(self.ctx.ndk_dir, self.ctx.python_recipe.version[0:3])
|
||||||
env['STRIP'] = '{}-strip --strip-unneeded'.format(command_prefix)
|
env['STRIP'] = '{}-strip --strip-unneeded'.format(command_prefix)
|
||||||
env['MAKE'] = 'make -j5'
|
env['MAKE'] = 'make -j5'
|
||||||
env['READELF'] = '{}-readelf'.format(command_prefix)
|
env['READELF'] = '{}-readelf'.format(command_prefix)
|
||||||
env['NM'] = '{}-nm'.format(command_prefix)
|
env['NM'] = '{}-nm'.format(command_prefix)
|
||||||
|
|
||||||
hostpython_recipe = Recipe.get_recipe('hostpython2', self.ctx)
|
hostpython_recipe = Recipe.get_recipe(
|
||||||
|
'host' + self.ctx.python_recipe.name, self.ctx)
|
||||||
# AND: This hardcodes python version 2.7, needs fixing
|
|
||||||
env['BUILDLIB_PATH'] = join(
|
env['BUILDLIB_PATH'] = join(
|
||||||
hostpython_recipe.get_build_dir(self.arch),
|
hostpython_recipe.get_build_dir(self.arch),
|
||||||
'build', 'lib.linux-{}-2.7'.format(uname()[-1]))
|
'build', 'lib.{}-{}'.format(
|
||||||
|
build_platform, self.ctx.python_recipe.major_minor_version_string)
|
||||||
|
)
|
||||||
|
|
||||||
env['PATH'] = environ['PATH']
|
env['PATH'] = environ['PATH']
|
||||||
|
|
||||||
env['ARCH'] = self.arch
|
env['ARCH'] = self.arch
|
||||||
|
env['NDK_API'] = 'android-{}'.format(str(self.ctx.ndk_api))
|
||||||
|
|
||||||
if self.ctx.python_recipe and self.ctx.python_recipe.from_crystax:
|
if self.ctx.python_recipe and self.ctx.python_recipe.from_crystax:
|
||||||
env['CRYSTAX_PYTHON_VERSION'] = self.ctx.python_recipe.version
|
env['CRYSTAX_PYTHON_VERSION'] = self.ctx.python_recipe.version
|
||||||
|
@ -123,12 +188,18 @@ class ArchARM(Arch):
|
||||||
command_prefix = 'arm-linux-androideabi'
|
command_prefix = 'arm-linux-androideabi'
|
||||||
platform_dir = 'arch-arm'
|
platform_dir = 'arch-arm'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target(self):
|
||||||
|
target_data = self.command_prefix.split('-')
|
||||||
|
return '-'.join(
|
||||||
|
['armv7a', 'none', target_data[1], target_data[2]])
|
||||||
|
|
||||||
|
|
||||||
class ArchARMv7_a(ArchARM):
|
class ArchARMv7_a(ArchARM):
|
||||||
arch = 'armeabi-v7a'
|
arch = 'armeabi-v7a'
|
||||||
|
|
||||||
def get_env(self, with_flags_in_cc=True):
|
def get_env(self, with_flags_in_cc=True, clang=False):
|
||||||
env = super(ArchARMv7_a, self).get_env(with_flags_in_cc)
|
env = super(ArchARMv7_a, self).get_env(with_flags_in_cc, clang=clang)
|
||||||
env['CFLAGS'] = (env['CFLAGS'] +
|
env['CFLAGS'] = (env['CFLAGS'] +
|
||||||
(' -march=armv7-a -mfloat-abi=softfp '
|
(' -march=armv7-a -mfloat-abi=softfp '
|
||||||
'-mfpu=vfp -mthumb'))
|
'-mfpu=vfp -mthumb'))
|
||||||
|
@ -142,8 +213,8 @@ class Archx86(Arch):
|
||||||
command_prefix = 'i686-linux-android'
|
command_prefix = 'i686-linux-android'
|
||||||
platform_dir = 'arch-x86'
|
platform_dir = 'arch-x86'
|
||||||
|
|
||||||
def get_env(self, with_flags_in_cc=True):
|
def get_env(self, with_flags_in_cc=True, clang=False):
|
||||||
env = super(Archx86, self).get_env(with_flags_in_cc)
|
env = super(Archx86, self).get_env(with_flags_in_cc, clang=clang)
|
||||||
env['CFLAGS'] = (env['CFLAGS'] +
|
env['CFLAGS'] = (env['CFLAGS'] +
|
||||||
' -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32')
|
' -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32')
|
||||||
env['CXXFLAGS'] = env['CFLAGS']
|
env['CXXFLAGS'] = env['CFLAGS']
|
||||||
|
@ -152,12 +223,12 @@ class Archx86(Arch):
|
||||||
|
|
||||||
class Archx86_64(Arch):
|
class Archx86_64(Arch):
|
||||||
arch = 'x86_64'
|
arch = 'x86_64'
|
||||||
toolchain_prefix = 'x86'
|
toolchain_prefix = 'x86_64'
|
||||||
command_prefix = 'x86_64-linux-android'
|
command_prefix = 'x86_64-linux-android'
|
||||||
platform_dir = 'arch-x86'
|
platform_dir = 'arch-x86_64'
|
||||||
|
|
||||||
def get_env(self, with_flags_in_cc=True):
|
def get_env(self, with_flags_in_cc=True, clang=False):
|
||||||
env = super(Archx86_64, self).get_env(with_flags_in_cc)
|
env = super(Archx86_64, self).get_env(with_flags_in_cc, clang=clang)
|
||||||
env['CFLAGS'] = (env['CFLAGS'] +
|
env['CFLAGS'] = (env['CFLAGS'] +
|
||||||
' -march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel')
|
' -march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel')
|
||||||
env['CXXFLAGS'] = env['CFLAGS']
|
env['CXXFLAGS'] = env['CFLAGS']
|
||||||
|
@ -170,8 +241,8 @@ class ArchAarch_64(Arch):
|
||||||
command_prefix = 'aarch64-linux-android'
|
command_prefix = 'aarch64-linux-android'
|
||||||
platform_dir = 'arch-arm64'
|
platform_dir = 'arch-arm64'
|
||||||
|
|
||||||
def get_env(self, with_flags_in_cc=True):
|
def get_env(self, with_flags_in_cc=True, clang=False):
|
||||||
env = super(ArchAarch_64, self).get_env(with_flags_in_cc)
|
env = super(ArchAarch_64, self).get_env(with_flags_in_cc, clang=clang)
|
||||||
incpath = ' -I' + join(dirname(__file__), 'includes', 'arm64-v8a')
|
incpath = ' -I' + join(dirname(__file__), 'includes', 'arm64-v8a')
|
||||||
env['EXTRA_CFLAGS'] = incpath
|
env['EXTRA_CFLAGS'] = incpath
|
||||||
env['CFLAGS'] += incpath
|
env['CFLAGS'] += incpath
|
||||||
|
|
|
@ -1,17 +1,39 @@
|
||||||
from os.path import (join, dirname, isdir, splitext, basename)
|
from os.path import (join, dirname, isdir, normpath, splitext, basename)
|
||||||
from os import listdir
|
from os import listdir, walk, sep
|
||||||
import sh
|
import sh
|
||||||
|
import shlex
|
||||||
import glob
|
import glob
|
||||||
import json
|
|
||||||
import importlib
|
import importlib
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
from pythonforandroid.logger import (warning, shprint, info, logger,
|
from pythonforandroid.logger import (warning, shprint, info, logger,
|
||||||
debug)
|
debug)
|
||||||
from pythonforandroid.util import (current_directory, ensure_dir,
|
from pythonforandroid.util import (current_directory, ensure_dir,
|
||||||
temp_directory, which)
|
temp_directory)
|
||||||
from pythonforandroid.recipe import Recipe
|
from pythonforandroid.recipe import Recipe
|
||||||
|
|
||||||
|
|
||||||
|
def copy_files(src_root, dest_root, override=True):
|
||||||
|
for root, dirnames, filenames in walk(src_root):
|
||||||
|
for filename in filenames:
|
||||||
|
subdir = normpath(root.replace(src_root, ""))
|
||||||
|
if subdir.startswith(sep): # ensure it is relative
|
||||||
|
subdir = subdir[1:]
|
||||||
|
dest_dir = join(dest_root, subdir)
|
||||||
|
if not os.path.exists(dest_dir):
|
||||||
|
os.makedirs(dest_dir)
|
||||||
|
src_file = join(root, filename)
|
||||||
|
dest_file = join(dest_dir, filename)
|
||||||
|
if os.path.isfile(src_file):
|
||||||
|
if override and os.path.exists(dest_file):
|
||||||
|
os.unlink(dest_file)
|
||||||
|
if not os.path.exists(dest_file):
|
||||||
|
shutil.copy(src_file, dest_file)
|
||||||
|
else:
|
||||||
|
os.makedirs(dest_file)
|
||||||
|
|
||||||
|
|
||||||
class Bootstrap(object):
|
class Bootstrap(object):
|
||||||
'''An Android project template, containing recipe stuff for
|
'''An Android project template, containing recipe stuff for
|
||||||
compilation and templated fields for APK info.
|
compilation and templated fields for APK info.
|
||||||
|
@ -27,7 +49,11 @@ class Bootstrap(object):
|
||||||
dist_name = None
|
dist_name = None
|
||||||
distribution = None
|
distribution = None
|
||||||
|
|
||||||
recipe_depends = ['sdl2']
|
# All bootstraps should include Python in some way:
|
||||||
|
recipe_depends = [
|
||||||
|
("python2", "python2legacy", "python3", "python3crystax"),
|
||||||
|
'android',
|
||||||
|
]
|
||||||
|
|
||||||
can_be_chosen_automatically = True
|
can_be_chosen_automatically = True
|
||||||
'''Determines whether the bootstrap can be chosen as one that
|
'''Determines whether the bootstrap can be chosen as one that
|
||||||
|
@ -78,6 +104,9 @@ class Bootstrap(object):
|
||||||
def get_dist_dir(self, name):
|
def get_dist_dir(self, name):
|
||||||
return join(self.ctx.dist_dir, name)
|
return join(self.ctx.dist_dir, name)
|
||||||
|
|
||||||
|
def get_common_dir(self):
|
||||||
|
return os.path.abspath(join(self.bootstrap_dir, "..", 'common'))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
modname = self.__class__.__module__
|
modname = self.__class__.__module__
|
||||||
|
@ -87,9 +116,10 @@ class Bootstrap(object):
|
||||||
'''Ensure that a build dir exists for the recipe. This same single
|
'''Ensure that a build dir exists for the recipe. This same single
|
||||||
dir will be used for building all different archs.'''
|
dir will be used for building all different archs.'''
|
||||||
self.build_dir = self.get_build_dir()
|
self.build_dir = self.get_build_dir()
|
||||||
shprint(sh.cp, '-r',
|
self.common_dir = self.get_common_dir()
|
||||||
join(self.bootstrap_dir, 'build'),
|
copy_files(join(self.bootstrap_dir, 'build'), self.build_dir)
|
||||||
self.build_dir)
|
copy_files(join(self.common_dir, 'build'), self.build_dir,
|
||||||
|
override=False)
|
||||||
if self.ctx.symlink_java_src:
|
if self.ctx.symlink_java_src:
|
||||||
info('Symlinking java src instead of copying')
|
info('Symlinking java src instead of copying')
|
||||||
shprint(sh.rm, '-r', join(self.build_dir, 'src'))
|
shprint(sh.rm, '-r', join(self.build_dir, 'src'))
|
||||||
|
@ -102,26 +132,15 @@ class Bootstrap(object):
|
||||||
fileh.write('target=android-{}'.format(self.ctx.android_api))
|
fileh.write('target=android-{}'.format(self.ctx.android_api))
|
||||||
|
|
||||||
def prepare_dist_dir(self, name):
|
def prepare_dist_dir(self, name):
|
||||||
# self.dist_dir = self.get_dist_dir(name)
|
|
||||||
ensure_dir(self.dist_dir)
|
ensure_dir(self.dist_dir)
|
||||||
|
|
||||||
def run_distribute(self):
|
def run_distribute(self):
|
||||||
# print('Default bootstrap being used doesn\'t know how '
|
self.distribution.save_info(self.dist_dir)
|
||||||
# 'to distribute...failing.')
|
|
||||||
# exit(1)
|
|
||||||
with current_directory(self.dist_dir):
|
|
||||||
info('Saving distribution info')
|
|
||||||
with open('dist_info.json', 'w') as fileh:
|
|
||||||
json.dump({'dist_name': self.ctx.dist_name,
|
|
||||||
'bootstrap': self.ctx.bootstrap.name,
|
|
||||||
'archs': [arch.arch for arch in self.ctx.archs],
|
|
||||||
'recipes': self.ctx.recipe_build_order + self.ctx.python_modules},
|
|
||||||
fileh)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def list_bootstraps(cls):
|
def list_bootstraps(cls):
|
||||||
'''Find all the available bootstraps and return them.'''
|
'''Find all the available bootstraps and return them.'''
|
||||||
forbidden_dirs = ('__pycache__', )
|
forbidden_dirs = ('__pycache__', 'common')
|
||||||
bootstraps_dir = join(dirname(__file__), 'bootstraps')
|
bootstraps_dir = join(dirname(__file__), 'bootstraps')
|
||||||
for name in listdir(bootstraps_dir):
|
for name in listdir(bootstraps_dir):
|
||||||
if name in forbidden_dirs:
|
if name in forbidden_dirs:
|
||||||
|
@ -152,7 +171,7 @@ class Bootstrap(object):
|
||||||
for recipe in recipes:
|
for recipe in recipes:
|
||||||
try:
|
try:
|
||||||
recipe = Recipe.get_recipe(recipe, ctx)
|
recipe = Recipe.get_recipe(recipe, ctx)
|
||||||
except IOError:
|
except ValueError:
|
||||||
conflicts = []
|
conflicts = []
|
||||||
else:
|
else:
|
||||||
conflicts = recipe.conflicts
|
conflicts = recipe.conflicts
|
||||||
|
@ -160,7 +179,7 @@ class Bootstrap(object):
|
||||||
for conflict in conflicts]):
|
for conflict in conflicts]):
|
||||||
ok = False
|
ok = False
|
||||||
break
|
break
|
||||||
if ok:
|
if ok and bs not in acceptable_bootstraps:
|
||||||
acceptable_bootstraps.append(bs)
|
acceptable_bootstraps.append(bs)
|
||||||
info('Found {} acceptable bootstraps: {}'.format(
|
info('Found {} acceptable bootstraps: {}'.format(
|
||||||
len(acceptable_bootstraps),
|
len(acceptable_bootstraps),
|
||||||
|
@ -249,16 +268,22 @@ class Bootstrap(object):
|
||||||
info('Python was loaded from CrystaX, skipping strip')
|
info('Python was loaded from CrystaX, skipping strip')
|
||||||
return
|
return
|
||||||
env = arch.get_env()
|
env = arch.get_env()
|
||||||
strip = which('arm-linux-androideabi-strip', env['PATH'])
|
tokens = shlex.split(env['STRIP'])
|
||||||
if strip is None:
|
strip = sh.Command(tokens[0])
|
||||||
warning('Can\'t find strip in PATH...')
|
if len(tokens) > 1:
|
||||||
return
|
strip = strip.bake(tokens[1:])
|
||||||
strip = sh.Command(strip)
|
|
||||||
filens = shprint(sh.find, join(self.dist_dir, 'private'),
|
libs_dir = join(self.dist_dir, '_python_bundle',
|
||||||
join(self.dist_dir, 'libs'),
|
'_python_bundle', 'modules')
|
||||||
|
if self.ctx.python_recipe.name == 'python2legacy':
|
||||||
|
libs_dir = join(self.dist_dir, 'private')
|
||||||
|
filens = shprint(sh.find, libs_dir, join(self.dist_dir, 'libs'),
|
||||||
'-iname', '*.so', _env=env).stdout.decode('utf-8')
|
'-iname', '*.so', _env=env).stdout.decode('utf-8')
|
||||||
|
|
||||||
logger.info('Stripping libraries in private dir')
|
logger.info('Stripping libraries in private dir')
|
||||||
for filen in filens.split('\n'):
|
for filen in filens.split('\n'):
|
||||||
|
if not filen:
|
||||||
|
continue # skip the last ''
|
||||||
try:
|
try:
|
||||||
strip(filen, _env=env)
|
strip(filen, _env=env)
|
||||||
except sh.ErrorReturnCode_1:
|
except sh.ErrorReturnCode_1:
|
||||||
|
|
22
p4a/pythonforandroid/bootstraps/common/build/ant.properties
Normal file
22
p4a/pythonforandroid/bootstraps/common/build/ant.properties
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# This file is used to override default values used by the Ant build system.
|
||||||
|
#
|
||||||
|
# This file must be checked into Version Control Systems, as it is
|
||||||
|
# integral to the build system of your project.
|
||||||
|
|
||||||
|
# This file is only used by the Ant script.
|
||||||
|
|
||||||
|
# You can use this to override default values such as
|
||||||
|
# 'source.dir' for the location of your java source folder and
|
||||||
|
# 'out.dir' for the location of your output folder.
|
||||||
|
|
||||||
|
# You can also use it define how the release builds are signed by declaring
|
||||||
|
# the following properties:
|
||||||
|
# 'key.store' for the location of your keystore and
|
||||||
|
# 'key.alias' for the name of the key to use.
|
||||||
|
# The password will be asked during the build when you use the 'release' target.
|
||||||
|
|
||||||
|
source.absolute.dir = tmp-src
|
||||||
|
|
||||||
|
resource.absolute.dir = src/main/res
|
||||||
|
|
||||||
|
asset.absolute.dir = src/main/assets
|
795
p4a/pythonforandroid/bootstraps/common/build/build.py
Normal file
795
p4a/pythonforandroid/bootstraps/common/build/build.py
Normal file
|
@ -0,0 +1,795 @@
|
||||||
|
#!/usr/bin/env python2.7
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import json
|
||||||
|
from os.path import (
|
||||||
|
dirname, join, isfile, realpath,
|
||||||
|
relpath, split, exists, basename
|
||||||
|
)
|
||||||
|
from os import listdir, makedirs, remove
|
||||||
|
import os
|
||||||
|
import shlex
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tarfile
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
from zipfile import ZipFile
|
||||||
|
|
||||||
|
from distutils.version import LooseVersion
|
||||||
|
from fnmatch import fnmatch
|
||||||
|
import jinja2
|
||||||
|
|
||||||
|
|
||||||
|
def get_dist_info_for(key):
|
||||||
|
try:
|
||||||
|
with open(join(dirname(__file__), 'dist_info.json'), 'r') as fileh:
|
||||||
|
info = json.load(fileh)
|
||||||
|
value = str(info[key])
|
||||||
|
except (OSError, KeyError) as e:
|
||||||
|
print("BUILD FAILURE: Couldn't extract the key `" + key + "` " +
|
||||||
|
"from dist_info.json: " + str(e))
|
||||||
|
sys.exit(1)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def get_hostpython():
|
||||||
|
return get_dist_info_for('hostpython')
|
||||||
|
|
||||||
|
|
||||||
|
def get_python_version():
|
||||||
|
return get_dist_info_for('python_version')
|
||||||
|
|
||||||
|
|
||||||
|
def get_bootstrap_name():
|
||||||
|
return get_dist_info_for('bootstrap')
|
||||||
|
|
||||||
|
|
||||||
|
if os.name == 'nt':
|
||||||
|
ANDROID = 'android.bat'
|
||||||
|
ANT = 'ant.bat'
|
||||||
|
else:
|
||||||
|
ANDROID = 'android'
|
||||||
|
ANT = 'ant'
|
||||||
|
|
||||||
|
curdir = dirname(__file__)
|
||||||
|
|
||||||
|
PYTHON = get_hostpython()
|
||||||
|
PYTHON_VERSION = get_python_version()
|
||||||
|
if PYTHON is not None and not exists(PYTHON):
|
||||||
|
PYTHON = None
|
||||||
|
|
||||||
|
BLACKLIST_PATTERNS = [
|
||||||
|
# code versionning
|
||||||
|
'^*.hg/*',
|
||||||
|
'^*.git/*',
|
||||||
|
'^*.bzr/*',
|
||||||
|
'^*.svn/*',
|
||||||
|
|
||||||
|
# temp files
|
||||||
|
'~',
|
||||||
|
'*.bak',
|
||||||
|
'*.swp',
|
||||||
|
]
|
||||||
|
# pyc/py
|
||||||
|
if PYTHON is not None:
|
||||||
|
BLACKLIST_PATTERNS.append('*.py')
|
||||||
|
if PYTHON_VERSION and int(PYTHON_VERSION[0]) == 2:
|
||||||
|
# we only blacklist `.pyc` for python2 because in python3 the compiled
|
||||||
|
# extension is `.pyc` (.pyo files not exists for python >= 3.6)
|
||||||
|
BLACKLIST_PATTERNS.append('*.pyc')
|
||||||
|
|
||||||
|
WHITELIST_PATTERNS = []
|
||||||
|
if get_bootstrap_name() in ('sdl2', 'webview', 'service_only'):
|
||||||
|
WHITELIST_PATTERNS.append('pyconfig.h')
|
||||||
|
|
||||||
|
python_files = []
|
||||||
|
|
||||||
|
|
||||||
|
environment = jinja2.Environment(loader=jinja2.FileSystemLoader(
|
||||||
|
join(curdir, 'templates')))
|
||||||
|
|
||||||
|
|
||||||
|
def try_unlink(fn):
|
||||||
|
if exists(fn):
|
||||||
|
os.unlink(fn)
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_dir(path):
|
||||||
|
if not exists(path):
|
||||||
|
makedirs(path)
|
||||||
|
|
||||||
|
|
||||||
|
def render(template, dest, **kwargs):
|
||||||
|
'''Using jinja2, render `template` to the filename `dest`, supplying the
|
||||||
|
|
||||||
|
keyword arguments as template parameters.
|
||||||
|
'''
|
||||||
|
|
||||||
|
dest_dir = dirname(dest)
|
||||||
|
if dest_dir and not exists(dest_dir):
|
||||||
|
makedirs(dest_dir)
|
||||||
|
|
||||||
|
template = environment.get_template(template)
|
||||||
|
text = template.render(**kwargs)
|
||||||
|
|
||||||
|
f = open(dest, 'wb')
|
||||||
|
f.write(text.encode('utf-8'))
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
def is_whitelist(name):
|
||||||
|
return match_filename(WHITELIST_PATTERNS, name)
|
||||||
|
|
||||||
|
|
||||||
|
def is_blacklist(name):
|
||||||
|
if is_whitelist(name):
|
||||||
|
return False
|
||||||
|
return match_filename(BLACKLIST_PATTERNS, name)
|
||||||
|
|
||||||
|
|
||||||
|
def match_filename(pattern_list, name):
|
||||||
|
for pattern in pattern_list:
|
||||||
|
if pattern.startswith('^'):
|
||||||
|
pattern = pattern[1:]
|
||||||
|
else:
|
||||||
|
pattern = '*/' + pattern
|
||||||
|
if fnmatch(name, pattern):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def listfiles(d):
|
||||||
|
basedir = d
|
||||||
|
subdirlist = []
|
||||||
|
for item in os.listdir(d):
|
||||||
|
fn = join(d, item)
|
||||||
|
if isfile(fn):
|
||||||
|
yield fn
|
||||||
|
else:
|
||||||
|
subdirlist.append(join(basedir, item))
|
||||||
|
for subdir in subdirlist:
|
||||||
|
for fn in listfiles(subdir):
|
||||||
|
yield fn
|
||||||
|
|
||||||
|
|
||||||
|
def make_python_zip():
|
||||||
|
'''
|
||||||
|
Search for all the python related files, and construct the pythonXX.zip
|
||||||
|
According to
|
||||||
|
# http://randomsplat.com/id5-cross-compiling-python-for-embedded-linux.html
|
||||||
|
site-packages, config and lib-dynload will be not included.
|
||||||
|
'''
|
||||||
|
|
||||||
|
if not exists('private'):
|
||||||
|
print('No compiled python is present to zip, skipping.')
|
||||||
|
return
|
||||||
|
|
||||||
|
global python_files
|
||||||
|
d = realpath(join('private', 'lib', 'python2.7'))
|
||||||
|
|
||||||
|
def select(fn):
|
||||||
|
if is_blacklist(fn):
|
||||||
|
return False
|
||||||
|
fn = realpath(fn)
|
||||||
|
assert(fn.startswith(d))
|
||||||
|
fn = fn[len(d):]
|
||||||
|
if (fn.startswith('/site-packages/')
|
||||||
|
or fn.startswith('/config/')
|
||||||
|
or fn.startswith('/lib-dynload/')
|
||||||
|
or fn.startswith('/libpymodules.so')):
|
||||||
|
return False
|
||||||
|
return fn
|
||||||
|
|
||||||
|
# get a list of all python file
|
||||||
|
python_files = [x for x in listfiles(d) if select(x)]
|
||||||
|
|
||||||
|
# create the final zipfile
|
||||||
|
zfn = join('private', 'lib', 'python27.zip')
|
||||||
|
zf = ZipFile(zfn, 'w')
|
||||||
|
|
||||||
|
# put all the python files in it
|
||||||
|
for fn in python_files:
|
||||||
|
afn = fn[len(d):]
|
||||||
|
zf.write(fn, afn)
|
||||||
|
zf.close()
|
||||||
|
|
||||||
|
|
||||||
|
def make_tar(tfn, source_dirs, ignore_path=[], optimize_python=True):
|
||||||
|
'''
|
||||||
|
Make a zip file `fn` from the contents of source_dis.
|
||||||
|
'''
|
||||||
|
|
||||||
|
# selector function
|
||||||
|
def select(fn):
|
||||||
|
rfn = realpath(fn)
|
||||||
|
for p in ignore_path:
|
||||||
|
if p.endswith('/'):
|
||||||
|
p = p[:-1]
|
||||||
|
if rfn.startswith(p):
|
||||||
|
return False
|
||||||
|
if rfn in python_files:
|
||||||
|
return False
|
||||||
|
return not is_blacklist(fn)
|
||||||
|
|
||||||
|
# get the files and relpath file of all the directory we asked for
|
||||||
|
files = []
|
||||||
|
for sd in source_dirs:
|
||||||
|
sd = realpath(sd)
|
||||||
|
compile_dir(sd, optimize_python=optimize_python)
|
||||||
|
files += [(x, relpath(realpath(x), sd)) for x in listfiles(sd)
|
||||||
|
if select(x)]
|
||||||
|
|
||||||
|
# create tar.gz of thoses files
|
||||||
|
tf = tarfile.open(tfn, 'w:gz', format=tarfile.USTAR_FORMAT)
|
||||||
|
dirs = []
|
||||||
|
for fn, afn in files:
|
||||||
|
dn = dirname(afn)
|
||||||
|
if dn not in dirs:
|
||||||
|
# create every dirs first if not exist yet
|
||||||
|
d = ''
|
||||||
|
for component in split(dn):
|
||||||
|
d = join(d, component)
|
||||||
|
if d.startswith('/'):
|
||||||
|
d = d[1:]
|
||||||
|
if d == '' or d in dirs:
|
||||||
|
continue
|
||||||
|
dirs.append(d)
|
||||||
|
tinfo = tarfile.TarInfo(d)
|
||||||
|
tinfo.type = tarfile.DIRTYPE
|
||||||
|
tf.addfile(tinfo)
|
||||||
|
|
||||||
|
# put the file
|
||||||
|
tf.add(fn, afn)
|
||||||
|
tf.close()
|
||||||
|
|
||||||
|
|
||||||
|
def compile_dir(dfn, optimize_python=True):
|
||||||
|
'''
|
||||||
|
Compile *.py in directory `dfn` to *.pyo
|
||||||
|
'''
|
||||||
|
|
||||||
|
if PYTHON is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if int(PYTHON_VERSION[0]) >= 3:
|
||||||
|
args = [PYTHON, '-m', 'compileall', '-b', '-f', dfn]
|
||||||
|
else:
|
||||||
|
args = [PYTHON, '-m', 'compileall', '-f', dfn]
|
||||||
|
if optimize_python:
|
||||||
|
# -OO = strip docstrings
|
||||||
|
args.insert(1, '-OO')
|
||||||
|
return_code = subprocess.call(args)
|
||||||
|
|
||||||
|
if return_code != 0:
|
||||||
|
print('Error while running "{}"'.format(' '.join(args)))
|
||||||
|
print('This probably means one of your Python files has a syntax '
|
||||||
|
'error, see logs above')
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def make_package(args):
|
||||||
|
# If no launcher is specified, require a main.py/main.pyo:
|
||||||
|
if (get_bootstrap_name() != "sdl" or args.launcher is None) and \
|
||||||
|
get_bootstrap_name() != "webview":
|
||||||
|
# (webview doesn't need an entrypoint, apparently)
|
||||||
|
if args.private is None or (
|
||||||
|
not exists(join(realpath(args.private), 'main.py')) and
|
||||||
|
not exists(join(realpath(args.private), 'main.pyo'))):
|
||||||
|
print('''BUILD FAILURE: No main.py(o) found in your app directory. This
|
||||||
|
file must exist to act as the entry point for you app. If your app is
|
||||||
|
started by a file with a different name, rename it to main.py or add a
|
||||||
|
main.py that loads it.''')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
assets_dir = "src/main/assets"
|
||||||
|
|
||||||
|
# Delete the old assets.
|
||||||
|
try_unlink(join(assets_dir, 'public.mp3'))
|
||||||
|
try_unlink(join(assets_dir, 'private.mp3'))
|
||||||
|
ensure_dir(assets_dir)
|
||||||
|
|
||||||
|
# In order to speedup import and initial depack,
|
||||||
|
# construct a python27.zip
|
||||||
|
make_python_zip()
|
||||||
|
|
||||||
|
# Add extra environment variable file into tar-able directory:
|
||||||
|
env_vars_tarpath = tempfile.mkdtemp(prefix="p4a-extra-env-")
|
||||||
|
with open(os.path.join(env_vars_tarpath, "p4a_env_vars.txt"), "w") as f:
|
||||||
|
f.write("P4A_IS_WINDOWED=" + str(args.window) + "\n")
|
||||||
|
if hasattr(args, "orientation"):
|
||||||
|
f.write("P4A_ORIENTATION=" + str(args.orientation) + "\n")
|
||||||
|
f.write("P4A_NUMERIC_VERSION=" + str(args.numeric_version) + "\n")
|
||||||
|
f.write("P4A_MINSDK=" + str(args.min_sdk_version) + "\n")
|
||||||
|
|
||||||
|
# Package up the private data (public not supported).
|
||||||
|
tar_dirs = [env_vars_tarpath]
|
||||||
|
if args.private:
|
||||||
|
tar_dirs.append(args.private)
|
||||||
|
for python_bundle_dir in ('private', 'crystax_python', '_python_bundle'):
|
||||||
|
if exists(python_bundle_dir):
|
||||||
|
tar_dirs.append(python_bundle_dir)
|
||||||
|
if get_bootstrap_name() == "webview":
|
||||||
|
tar_dirs.append('webview_includes')
|
||||||
|
if args.private or args.launcher:
|
||||||
|
make_tar(
|
||||||
|
join(assets_dir, 'private.mp3'), tar_dirs, args.ignore_path,
|
||||||
|
optimize_python=args.optimize_python)
|
||||||
|
|
||||||
|
# Remove extra env vars tar-able directory:
|
||||||
|
shutil.rmtree(env_vars_tarpath)
|
||||||
|
|
||||||
|
# Prepare some variables for templating process
|
||||||
|
res_dir = "src/main/res"
|
||||||
|
default_icon = 'templates/kivy-icon.png'
|
||||||
|
default_presplash = 'templates/kivy-presplash.jpg'
|
||||||
|
shutil.copy(
|
||||||
|
args.icon or default_icon,
|
||||||
|
join(res_dir, 'drawable/icon.png')
|
||||||
|
)
|
||||||
|
if get_bootstrap_name() != "service_only":
|
||||||
|
shutil.copy(
|
||||||
|
args.presplash or default_presplash,
|
||||||
|
join(res_dir, 'drawable/presplash.jpg')
|
||||||
|
)
|
||||||
|
|
||||||
|
# If extra Java jars were requested, copy them into the libs directory
|
||||||
|
jars = []
|
||||||
|
if args.add_jar:
|
||||||
|
for jarname in args.add_jar:
|
||||||
|
if not exists(jarname):
|
||||||
|
print('Requested jar does not exist: {}'.format(jarname))
|
||||||
|
sys.exit(-1)
|
||||||
|
shutil.copy(jarname, 'src/main/libs')
|
||||||
|
jars.append(basename(jarname))
|
||||||
|
|
||||||
|
# If extra aar were requested, copy them into the libs directory
|
||||||
|
aars = []
|
||||||
|
if args.add_aar:
|
||||||
|
ensure_dir("libs")
|
||||||
|
for aarname in args.add_aar:
|
||||||
|
if not exists(aarname):
|
||||||
|
print('Requested aar does not exists: {}'.format(aarname))
|
||||||
|
sys.exit(-1)
|
||||||
|
shutil.copy(aarname, 'libs')
|
||||||
|
aars.append(basename(aarname).rsplit('.', 1)[0])
|
||||||
|
|
||||||
|
versioned_name = (args.name.replace(' ', '').replace('\'', '') +
|
||||||
|
'-' + args.version)
|
||||||
|
|
||||||
|
version_code = 0
|
||||||
|
if not args.numeric_version:
|
||||||
|
# Set version code in format (arch-minsdk-app_version)
|
||||||
|
with open(join(dirname(__file__), 'dist_info.json'), 'r') as dist_info:
|
||||||
|
dist_data = json.load(dist_info)
|
||||||
|
arch = dist_data["archs"][0]
|
||||||
|
arch_dict = {"x86_64": "9", "arm64-v8a": "8", "armeabi-v7a": "7", "x86": "6"}
|
||||||
|
arch_code = arch_dict.get(arch, '1')
|
||||||
|
min_sdk = args.min_sdk_version
|
||||||
|
for i in args.version.split('.'):
|
||||||
|
version_code *= 100
|
||||||
|
version_code += int(i)
|
||||||
|
args.numeric_version = "{}{}{}".format(arch_code, min_sdk, version_code)
|
||||||
|
|
||||||
|
if args.intent_filters:
|
||||||
|
with open(args.intent_filters) as fd:
|
||||||
|
args.intent_filters = fd.read()
|
||||||
|
|
||||||
|
if not args.add_activity:
|
||||||
|
args.add_activity = []
|
||||||
|
|
||||||
|
if not args.activity_launch_mode:
|
||||||
|
args.activity_launch_mode = ''
|
||||||
|
|
||||||
|
if args.extra_source_dirs:
|
||||||
|
esd = []
|
||||||
|
for spec in args.extra_source_dirs:
|
||||||
|
if ':' in spec:
|
||||||
|
specdir, specincludes = spec.split(':')
|
||||||
|
else:
|
||||||
|
specdir = spec
|
||||||
|
specincludes = '**'
|
||||||
|
esd.append((realpath(specdir), specincludes))
|
||||||
|
args.extra_source_dirs = esd
|
||||||
|
else:
|
||||||
|
args.extra_source_dirs = []
|
||||||
|
|
||||||
|
service = False
|
||||||
|
if args.private:
|
||||||
|
service_main = join(realpath(args.private), 'service', 'main.py')
|
||||||
|
if exists(service_main) or exists(service_main + 'o'):
|
||||||
|
service = True
|
||||||
|
|
||||||
|
service_names = []
|
||||||
|
for sid, spec in enumerate(args.services):
|
||||||
|
spec = spec.split(':')
|
||||||
|
name = spec[0]
|
||||||
|
entrypoint = spec[1]
|
||||||
|
options = spec[2:]
|
||||||
|
|
||||||
|
foreground = 'foreground' in options
|
||||||
|
sticky = 'sticky' in options
|
||||||
|
|
||||||
|
service_names.append(name)
|
||||||
|
service_target_path =\
|
||||||
|
'src/main/java/{}/Service{}.java'.format(
|
||||||
|
args.package.replace(".", "/"),
|
||||||
|
name.capitalize()
|
||||||
|
)
|
||||||
|
render(
|
||||||
|
'Service.tmpl.java',
|
||||||
|
service_target_path,
|
||||||
|
name=name,
|
||||||
|
entrypoint=entrypoint,
|
||||||
|
args=args,
|
||||||
|
foreground=foreground,
|
||||||
|
sticky=sticky,
|
||||||
|
service_id=sid + 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Find the SDK directory and target API
|
||||||
|
with open('project.properties', 'r') as fileh:
|
||||||
|
target = fileh.read().strip()
|
||||||
|
android_api = target.split('-')[1]
|
||||||
|
try:
|
||||||
|
int(android_api)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
raise ValueError(
|
||||||
|
"failed to extract the Android API level from " +
|
||||||
|
"build.properties. expected int, got: '" +
|
||||||
|
str(android_api) + "'"
|
||||||
|
)
|
||||||
|
with open('local.properties', 'r') as fileh:
|
||||||
|
sdk_dir = fileh.read().strip()
|
||||||
|
sdk_dir = sdk_dir[8:]
|
||||||
|
|
||||||
|
# Try to build with the newest available build tools
|
||||||
|
ignored = {".DS_Store", ".ds_store"}
|
||||||
|
build_tools_versions = [x for x in listdir(join(sdk_dir, 'build-tools')) if x not in ignored]
|
||||||
|
build_tools_versions = sorted(build_tools_versions,
|
||||||
|
key=LooseVersion)
|
||||||
|
build_tools_version = build_tools_versions[-1]
|
||||||
|
|
||||||
|
# Folder name for launcher (used by SDL2 bootstrap)
|
||||||
|
url_scheme = 'kivy'
|
||||||
|
|
||||||
|
# Render out android manifest:
|
||||||
|
manifest_path = "src/main/AndroidManifest.xml"
|
||||||
|
render_args = {
|
||||||
|
"args": args,
|
||||||
|
"service": service,
|
||||||
|
"service_names": service_names,
|
||||||
|
"android_api": android_api
|
||||||
|
}
|
||||||
|
if get_bootstrap_name() == "sdl2":
|
||||||
|
render_args["url_scheme"] = url_scheme
|
||||||
|
render(
|
||||||
|
'AndroidManifest.tmpl.xml',
|
||||||
|
manifest_path,
|
||||||
|
**render_args)
|
||||||
|
|
||||||
|
# Copy the AndroidManifest.xml to the dist root dir so that ant
|
||||||
|
# can also use it
|
||||||
|
if exists('AndroidManifest.xml'):
|
||||||
|
remove('AndroidManifest.xml')
|
||||||
|
shutil.copy(manifest_path, 'AndroidManifest.xml')
|
||||||
|
|
||||||
|
# gradle build templates
|
||||||
|
render(
|
||||||
|
'build.tmpl.gradle',
|
||||||
|
'build.gradle',
|
||||||
|
args=args,
|
||||||
|
aars=aars,
|
||||||
|
jars=jars,
|
||||||
|
android_api=android_api,
|
||||||
|
build_tools_version=build_tools_version
|
||||||
|
)
|
||||||
|
|
||||||
|
# ant build templates
|
||||||
|
render(
|
||||||
|
'build.tmpl.xml',
|
||||||
|
'build.xml',
|
||||||
|
args=args,
|
||||||
|
versioned_name=versioned_name)
|
||||||
|
|
||||||
|
# String resources:
|
||||||
|
render_args = {
|
||||||
|
"args": args,
|
||||||
|
"private_version": str(time.time())
|
||||||
|
}
|
||||||
|
if get_bootstrap_name() == "sdl2":
|
||||||
|
render_args["url_scheme"] = url_scheme
|
||||||
|
render(
|
||||||
|
'strings.tmpl.xml',
|
||||||
|
join(res_dir, 'values/strings.xml'),
|
||||||
|
**render_args)
|
||||||
|
|
||||||
|
if exists(join("templates", "custom_rules.tmpl.xml")):
|
||||||
|
render(
|
||||||
|
'custom_rules.tmpl.xml',
|
||||||
|
'custom_rules.xml',
|
||||||
|
args=args)
|
||||||
|
|
||||||
|
if get_bootstrap_name() == "webview":
|
||||||
|
render('WebViewLoader.tmpl.java',
|
||||||
|
'src/main/java/org/kivy/android/WebViewLoader.java',
|
||||||
|
args=args)
|
||||||
|
|
||||||
|
if args.sign:
|
||||||
|
render('build.properties', 'build.properties')
|
||||||
|
else:
|
||||||
|
if exists('build.properties'):
|
||||||
|
os.remove('build.properties')
|
||||||
|
|
||||||
|
# Apply java source patches if any are present:
|
||||||
|
if exists(join('src', 'patches')):
|
||||||
|
print("Applying Java source code patches...")
|
||||||
|
for patch_name in os.listdir(join('src', 'patches')):
|
||||||
|
patch_path = join('src', 'patches', patch_name)
|
||||||
|
print("Applying patch: " + str(patch_path))
|
||||||
|
try:
|
||||||
|
subprocess.check_output([
|
||||||
|
# -N: insist this is FORWARd patch, don't reverse apply
|
||||||
|
# -p1: strip first path component
|
||||||
|
# -t: batch mode, don't ask questions
|
||||||
|
"patch", "-N", "-p1", "-t", "-i", patch_path
|
||||||
|
])
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
if e.returncode == 1:
|
||||||
|
# Return code 1 means it didn't apply, this will
|
||||||
|
# usually mean it is already applied.
|
||||||
|
print("Warning: failed to apply patch (" +
|
||||||
|
"exit code 1), " +
|
||||||
|
"assuming it is already applied: " +
|
||||||
|
str(patch_path)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args(args=None):
|
||||||
|
global BLACKLIST_PATTERNS, WHITELIST_PATTERNS, PYTHON
|
||||||
|
|
||||||
|
# Get the default minsdk, equal to the NDK API that this dist is built against
|
||||||
|
try:
|
||||||
|
with open('dist_info.json', 'r') as fileh:
|
||||||
|
info = json.load(fileh)
|
||||||
|
default_min_api = int(info['ndk_api'])
|
||||||
|
ndk_api = default_min_api
|
||||||
|
except (OSError, KeyError, ValueError, TypeError):
|
||||||
|
print('WARNING: Failed to read ndk_api from dist info, defaulting to 12')
|
||||||
|
default_min_api = 12 # The old default before ndk_api was introduced
|
||||||
|
ndk_api = 12
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
ap = argparse.ArgumentParser(description='''\
|
||||||
|
Package a Python application for Android (using
|
||||||
|
bootstrap ''' + get_bootstrap_name() + ''').
|
||||||
|
|
||||||
|
For this to work, Java and Ant need to be in your path, as does the
|
||||||
|
tools directory of the Android SDK.
|
||||||
|
''')
|
||||||
|
|
||||||
|
# --private is required unless for sdl2, where there's also --launcher
|
||||||
|
ap.add_argument('--private', dest='private',
|
||||||
|
help='the directory with the app source code files' +
|
||||||
|
' (containing your main.py entrypoint)',
|
||||||
|
required=(get_bootstrap_name() != "sdl2"))
|
||||||
|
ap.add_argument('--package', dest='package',
|
||||||
|
help=('The name of the java package the project will be'
|
||||||
|
' packaged under.'),
|
||||||
|
required=True)
|
||||||
|
ap.add_argument('--name', dest='name',
|
||||||
|
help=('The human-readable name of the project.'),
|
||||||
|
required=True)
|
||||||
|
ap.add_argument('--numeric-version', dest='numeric_version',
|
||||||
|
help=('The numeric version number of the project. If not '
|
||||||
|
'given, this is automatically computed from the '
|
||||||
|
'version.'))
|
||||||
|
ap.add_argument('--version', dest='version',
|
||||||
|
help=('The version number of the project. This should '
|
||||||
|
'consist of numbers and dots, and should have the '
|
||||||
|
'same number of groups of numbers as previous '
|
||||||
|
'versions.'),
|
||||||
|
required=True)
|
||||||
|
if get_bootstrap_name() == "sdl2":
|
||||||
|
ap.add_argument('--launcher', dest='launcher', action='store_true',
|
||||||
|
help=('Provide this argument to build a multi-app '
|
||||||
|
'launcher, rather than a single app.'))
|
||||||
|
ap.add_argument('--permission', dest='permissions', action='append', default=[],
|
||||||
|
help='The permissions to give this app.', nargs='+')
|
||||||
|
ap.add_argument('--meta-data', dest='meta_data', action='append', default=[],
|
||||||
|
help='Custom key=value to add in application metadata')
|
||||||
|
ap.add_argument('--uses-library', dest='android_used_libs', action='append', default=[],
|
||||||
|
help='Used shared libraries included using <uses-library> tag in AndroidManifest.xml')
|
||||||
|
ap.add_argument('--icon', dest='icon',
|
||||||
|
help=('A png file to use as the icon for '
|
||||||
|
'the application.'))
|
||||||
|
ap.add_argument('--service', dest='services', action='append', default=[],
|
||||||
|
help='Declare a new service entrypoint: '
|
||||||
|
'NAME:PATH_TO_PY[:foreground]')
|
||||||
|
if get_bootstrap_name() != "service_only":
|
||||||
|
ap.add_argument('--presplash', dest='presplash',
|
||||||
|
help=('A jpeg file to use as a screen while the '
|
||||||
|
'application is loading.'))
|
||||||
|
ap.add_argument('--presplash-color',
|
||||||
|
dest='presplash_color',
|
||||||
|
default='#000000',
|
||||||
|
help=('A string to set the loading screen '
|
||||||
|
'background color. '
|
||||||
|
'Supported formats are: '
|
||||||
|
'#RRGGBB #AARRGGBB or color names '
|
||||||
|
'like red, green, blue, etc.'))
|
||||||
|
ap.add_argument('--window', dest='window', action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Indicate if the application will be windowed')
|
||||||
|
ap.add_argument('--orientation', dest='orientation',
|
||||||
|
default='portrait',
|
||||||
|
help=('The orientation that the game will '
|
||||||
|
'display in. '
|
||||||
|
'Usually one of "landscape", "portrait", '
|
||||||
|
'"sensor", or "user" (the same as "sensor" '
|
||||||
|
'but obeying the '
|
||||||
|
'user\'s Android rotation setting). '
|
||||||
|
'The full list of options is given under '
|
||||||
|
'android_screenOrientation at '
|
||||||
|
'https://developer.android.com/guide/'
|
||||||
|
'topics/manifest/'
|
||||||
|
'activity-element.html'))
|
||||||
|
ap.add_argument('--wakelock', dest='wakelock', action='store_true',
|
||||||
|
help=('Indicate if the application needs the device '
|
||||||
|
'to stay on'))
|
||||||
|
ap.add_argument('--blacklist', dest='blacklist',
|
||||||
|
default=join(curdir, 'blacklist.txt'),
|
||||||
|
help=('Use a blacklist file to match unwanted file in '
|
||||||
|
'the final APK'))
|
||||||
|
ap.add_argument('--whitelist', dest='whitelist',
|
||||||
|
default=join(curdir, 'whitelist.txt'),
|
||||||
|
help=('Use a whitelist file to prevent blacklisting of '
|
||||||
|
'file in the final APK'))
|
||||||
|
ap.add_argument('--add-jar', dest='add_jar', action='append',
|
||||||
|
help=('Add a Java .jar to the libs, so you can access its '
|
||||||
|
'classes with pyjnius. You can specify this '
|
||||||
|
'argument more than once to include multiple jars'))
|
||||||
|
ap.add_argument('--add-aar', dest='add_aar', action='append',
|
||||||
|
help=('Add an aar dependency manually'))
|
||||||
|
ap.add_argument('--depend', dest='depends', action='append',
|
||||||
|
help=('Add a external dependency '
|
||||||
|
'(eg: com.android.support:appcompat-v7:19.0.1)'))
|
||||||
|
# The --sdk option has been removed, it is ignored in favour of
|
||||||
|
# --android-api handled by toolchain.py
|
||||||
|
ap.add_argument('--sdk', dest='sdk_version', default=-1,
|
||||||
|
type=int, help=('Deprecated argument, does nothing'))
|
||||||
|
ap.add_argument('--minsdk', dest='min_sdk_version',
|
||||||
|
default=default_min_api, type=int,
|
||||||
|
help=('Minimum Android SDK version that the app supports. '
|
||||||
|
'Defaults to {}.'.format(default_min_api)))
|
||||||
|
ap.add_argument('--allow-minsdk-ndkapi-mismatch', default=False,
|
||||||
|
action='store_true',
|
||||||
|
help=('Allow the --minsdk argument to be different from '
|
||||||
|
'the discovered ndk_api in the dist'))
|
||||||
|
ap.add_argument('--intent-filters', dest='intent_filters',
|
||||||
|
help=('Add intent-filters xml rules to the '
|
||||||
|
'AndroidManifest.xml file. The argument is a '
|
||||||
|
'filename containing xml. The filename should be '
|
||||||
|
'located relative to the python-for-android '
|
||||||
|
'directory'))
|
||||||
|
ap.add_argument('--with-billing', dest='billing_pubkey',
|
||||||
|
help='If set, the billing service will be added (not implemented)')
|
||||||
|
ap.add_argument('--add-source', dest='extra_source_dirs', action='append',
|
||||||
|
help='Include additional source dirs in Java build')
|
||||||
|
if get_bootstrap_name() == "webview":
|
||||||
|
ap.add_argument('--port',
|
||||||
|
help='The port on localhost that the WebView will access',
|
||||||
|
default='5000')
|
||||||
|
ap.add_argument('--try-system-python-compile', dest='try_system_python_compile',
|
||||||
|
action='store_true',
|
||||||
|
help='Use the system python during compileall if possible.')
|
||||||
|
ap.add_argument('--no-compile-pyo', dest='no_compile_pyo', action='store_true',
|
||||||
|
help='Do not optimise .py files to .pyo.')
|
||||||
|
ap.add_argument('--sign', action='store_true',
|
||||||
|
help=('Try to sign the APK with your credentials. You must set '
|
||||||
|
'the appropriate environment variables.'))
|
||||||
|
ap.add_argument('--add-activity', dest='add_activity', action='append',
|
||||||
|
help='Add this Java class as an Activity to the manifest.')
|
||||||
|
ap.add_argument('--activity-launch-mode',
|
||||||
|
dest='activity_launch_mode',
|
||||||
|
default='singleTask',
|
||||||
|
help='Set the launch mode of the main activity in the manifest.')
|
||||||
|
ap.add_argument('--allow-backup', dest='allow_backup', default='true',
|
||||||
|
help="if set to 'false', then android won't backup the application.")
|
||||||
|
ap.add_argument('--no-optimize-python', dest='optimize_python',
|
||||||
|
action='store_false', default=True,
|
||||||
|
help=('Whether to compile to optimised .pyo files, using -OO '
|
||||||
|
'(strips docstrings and asserts)'))
|
||||||
|
|
||||||
|
# Put together arguments, and add those from .p4a config file:
|
||||||
|
if args is None:
|
||||||
|
args = sys.argv[1:]
|
||||||
|
|
||||||
|
def _read_configuration():
|
||||||
|
if not exists(".p4a"):
|
||||||
|
return
|
||||||
|
print("Reading .p4a configuration")
|
||||||
|
with open(".p4a") as fd:
|
||||||
|
lines = fd.readlines()
|
||||||
|
lines = [shlex.split(line)
|
||||||
|
for line in lines if not line.startswith("#")]
|
||||||
|
for line in lines:
|
||||||
|
for arg in line:
|
||||||
|
args.append(arg)
|
||||||
|
_read_configuration()
|
||||||
|
|
||||||
|
args = ap.parse_args(args)
|
||||||
|
args.ignore_path = []
|
||||||
|
|
||||||
|
if args.name and args.name[0] == '"' and args.name[-1] == '"':
|
||||||
|
args.name = args.name[1:-1]
|
||||||
|
|
||||||
|
if ndk_api != args.min_sdk_version:
|
||||||
|
print(('WARNING: --minsdk argument does not match the api that is '
|
||||||
|
'compiled against. Only proceed if you know what you are '
|
||||||
|
'doing, otherwise use --minsdk={} or recompile against api '
|
||||||
|
'{}').format(ndk_api, args.min_sdk_version))
|
||||||
|
if not args.allow_minsdk_ndkapi_mismatch:
|
||||||
|
print('You must pass --allow-minsdk-ndkapi-mismatch to build '
|
||||||
|
'with --minsdk different to the target NDK api from the '
|
||||||
|
'build step')
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
print('Proceeding with --minsdk not matching build target api')
|
||||||
|
|
||||||
|
if args.billing_pubkey:
|
||||||
|
print('Billing not yet supported!')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if args.sdk_version == -1:
|
||||||
|
print('WARNING: Received a --sdk argument, but this argument is '
|
||||||
|
'deprecated and does nothing.')
|
||||||
|
args.sdk_version = -1 # ensure it is not used
|
||||||
|
|
||||||
|
if args.permissions and isinstance(args.permissions[0], list):
|
||||||
|
args.permissions = [p for perm in args.permissions for p in perm]
|
||||||
|
|
||||||
|
if args.try_system_python_compile:
|
||||||
|
# Hardcoding python2.7 is okay for now, as python3 skips the
|
||||||
|
# compilation anyway
|
||||||
|
if not exists('crystax_python'):
|
||||||
|
python_executable = 'python2.7'
|
||||||
|
try:
|
||||||
|
subprocess.call([python_executable, '--version'])
|
||||||
|
except (OSError, subprocess.CalledProcessError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
PYTHON = python_executable
|
||||||
|
|
||||||
|
if args.no_compile_pyo:
|
||||||
|
PYTHON = None
|
||||||
|
BLACKLIST_PATTERNS.remove('*.py')
|
||||||
|
|
||||||
|
if args.blacklist:
|
||||||
|
with open(args.blacklist) as fd:
|
||||||
|
patterns = [x.strip() for x in fd.read().splitlines()
|
||||||
|
if x.strip() and not x.strip().startswith('#')]
|
||||||
|
BLACKLIST_PATTERNS += patterns
|
||||||
|
|
||||||
|
if args.whitelist:
|
||||||
|
with open(args.whitelist) as fd:
|
||||||
|
patterns = [x.strip() for x in fd.read().splitlines()
|
||||||
|
if x.strip() and not x.strip().startswith('#')]
|
||||||
|
WHITELIST_PATTERNS += patterns
|
||||||
|
|
||||||
|
if args.private is None and \
|
||||||
|
get_bootstrap_name() == 'sdl2' and args.launcher is None:
|
||||||
|
print('Need --private directory or ' +
|
||||||
|
'--launcher (SDL2 bootstrap only)' +
|
||||||
|
'to have something to launch inside the .apk!')
|
||||||
|
sys.exit(1)
|
||||||
|
make_package(args)
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parse_args()
|
BIN
p4a/pythonforandroid/bootstraps/common/build/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
p4a/pythonforandroid/bootstraps/common/build/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
p4a/pythonforandroid/bootstraps/common/build/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
p4a/pythonforandroid/bootstraps/common/build/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#Mon Mar 09 17:19:02 CET 2015
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
|
164
p4a/pythonforandroid/bootstraps/common/build/gradlew
vendored
Executable file
164
p4a/pythonforandroid/bootstraps/common/build/gradlew
vendored
Executable file
|
@ -0,0 +1,164 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS=""
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn ( ) {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die ( ) {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||||
|
if $cygwin ; then
|
||||||
|
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >&-
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >&-
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||||
|
function splitJvmOpts() {
|
||||||
|
JVM_OPTS=("$@")
|
||||||
|
}
|
||||||
|
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||||
|
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||||
|
|
||||||
|
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
90
p4a/pythonforandroid/bootstraps/common/build/gradlew.bat
vendored
Normal file
90
p4a/pythonforandroid/bootstraps/common/build/gradlew.bat
vendored
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS=
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windowz variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
goto execute
|
||||||
|
|
||||||
|
:4NT_args
|
||||||
|
@rem Get arguments from the 4NT Shell from JP Software
|
||||||
|
set CMD_LINE_ARGS=%$
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
|
@ -0,0 +1 @@
|
||||||
|
include $(call all-subdir-makefiles)
|
|
@ -0,0 +1 @@
|
||||||
|
include $(call all-subdir-makefiles)
|
|
@ -0,0 +1,27 @@
|
||||||
|
LOCAL_PATH := $(call my-dir)
|
||||||
|
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
LOCAL_MODULE := main
|
||||||
|
|
||||||
|
SDL_PATH := ../../SDL
|
||||||
|
|
||||||
|
LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include
|
||||||
|
|
||||||
|
# Add your application source files here...
|
||||||
|
LOCAL_SRC_FILES := $(SDL_PATH)/src/main/android/SDL_android_main.c \
|
||||||
|
start.c
|
||||||
|
|
||||||
|
LOCAL_CFLAGS += -I$(PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS)
|
||||||
|
|
||||||
|
LOCAL_SHARED_LIBRARIES := SDL2 python_shared
|
||||||
|
|
||||||
|
LOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -llog $(EXTRA_LDLIBS)
|
||||||
|
|
||||||
|
LOCAL_LDFLAGS += -L$(PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS)
|
||||||
|
|
||||||
|
include $(BUILD_SHARED_LIBRARY)
|
||||||
|
|
||||||
|
ifdef CRYSTAX_PYTHON_VERSION
|
||||||
|
$(call import-module,python/$(CRYSTAX_PYTHON_VERSION))
|
||||||
|
endif
|
|
@ -1,5 +1,4 @@
|
||||||
|
|
||||||
|
|
||||||
#define PY_SSIZE_T_CLEAN
|
#define PY_SSIZE_T_CLEAN
|
||||||
#include "Python.h"
|
#include "Python.h"
|
||||||
#ifndef Py_PYTHON_H
|
#ifndef Py_PYTHON_H
|
||||||
|
@ -15,6 +14,16 @@
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include "bootstrap_name.h"
|
||||||
|
#ifndef BOOTSTRAP_USES_NO_SDL_HEADERS
|
||||||
|
#include "SDL.h"
|
||||||
|
#ifndef BOOTSTRAP_NAME_PYGAME
|
||||||
|
#include "SDL_opengles2.h"
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#ifdef BOOTSTRAP_NAME_PYGAME
|
||||||
|
#include "jniwrapperstuff.h"
|
||||||
|
#endif
|
||||||
#include "android/log.h"
|
#include "android/log.h"
|
||||||
|
|
||||||
#define ENTRYPOINT_MAXLEN 128
|
#define ENTRYPOINT_MAXLEN 128
|
||||||
|
@ -58,7 +67,7 @@ int dir_exists(char *filename) {
|
||||||
|
|
||||||
int file_exists(const char *filename) {
|
int file_exists(const char *filename) {
|
||||||
FILE *file;
|
FILE *file;
|
||||||
if (file = fopen(filename, "r")) {
|
if ((file = fopen(filename, "r"))) {
|
||||||
fclose(file);
|
fclose(file);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -75,25 +84,79 @@ int main(int argc, char *argv[]) {
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
FILE *fd;
|
FILE *fd;
|
||||||
|
|
||||||
/* AND: Several filepaths are hardcoded here, these must be made
|
LOGP("Initializing Python for Android");
|
||||||
configurable */
|
|
||||||
/* AND: P4A uses env vars...not sure what's best */
|
// Set a couple of built-in environment vars:
|
||||||
LOGP("Initialize Python for Android");
|
setenv("P4A_BOOTSTRAP", bootstrap_name, 1); // env var to identify p4a to applications
|
||||||
env_argument = getenv("ANDROID_ARGUMENT");
|
env_argument = getenv("ANDROID_ARGUMENT");
|
||||||
setenv("ANDROID_APP_PATH", env_argument, 1);
|
setenv("ANDROID_APP_PATH", env_argument, 1);
|
||||||
env_entrypoint = getenv("ANDROID_ENTRYPOINT");
|
env_entrypoint = getenv("ANDROID_ENTRYPOINT");
|
||||||
env_logname = getenv("PYTHON_NAME");
|
env_logname = getenv("PYTHON_NAME");
|
||||||
|
if (!getenv("ANDROID_UNPACK")) {
|
||||||
|
/* ANDROID_UNPACK currently isn't set in services */
|
||||||
|
setenv("ANDROID_UNPACK", env_argument, 1);
|
||||||
|
}
|
||||||
if (env_logname == NULL) {
|
if (env_logname == NULL) {
|
||||||
env_logname = "python";
|
env_logname = "python";
|
||||||
setenv("PYTHON_NAME", "python", 1);
|
setenv("PYTHON_NAME", "python", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set additional file-provided environment vars:
|
||||||
|
LOGP("Setting additional env vars from p4a_env_vars.txt");
|
||||||
|
char env_file_path[256];
|
||||||
|
snprintf(env_file_path, sizeof(env_file_path),
|
||||||
|
"%s/p4a_env_vars.txt", getenv("ANDROID_UNPACK"));
|
||||||
|
FILE *env_file_fd = fopen(env_file_path, "r");
|
||||||
|
if (env_file_fd) {
|
||||||
|
char* line = NULL;
|
||||||
|
size_t len = 0;
|
||||||
|
while (getline(&line, &len, env_file_fd) != -1) {
|
||||||
|
if (strlen(line) > 0) {
|
||||||
|
char *eqsubstr = strstr(line, "=");
|
||||||
|
if (eqsubstr) {
|
||||||
|
size_t eq_pos = eqsubstr - line;
|
||||||
|
|
||||||
|
// Extract name:
|
||||||
|
char env_name[256];
|
||||||
|
strncpy(env_name, line, sizeof(env_name));
|
||||||
|
env_name[eq_pos] = '\0';
|
||||||
|
|
||||||
|
// Extract value (with line break removed:
|
||||||
|
char env_value[256];
|
||||||
|
strncpy(env_value, (char*)(line + eq_pos + 1), sizeof(env_value));
|
||||||
|
if (strlen(env_value) > 0 &&
|
||||||
|
env_value[strlen(env_value)-1] == '\n') {
|
||||||
|
env_value[strlen(env_value)-1] = '\0';
|
||||||
|
if (strlen(env_value) > 0 &&
|
||||||
|
env_value[strlen(env_value)-1] == '\r') {
|
||||||
|
// Also remove windows line breaks (\r\n)
|
||||||
|
env_value[strlen(env_value)-1] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set value:
|
||||||
|
setenv(env_name, env_value, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose(env_file_fd);
|
||||||
|
} else {
|
||||||
|
LOGP("Warning: no p4a_env_vars.txt found / failed to open!");
|
||||||
|
}
|
||||||
|
|
||||||
LOGP("Changing directory to the one provided by ANDROID_ARGUMENT");
|
LOGP("Changing directory to the one provided by ANDROID_ARGUMENT");
|
||||||
LOGP(env_argument);
|
LOGP(env_argument);
|
||||||
chdir(env_argument);
|
chdir(env_argument);
|
||||||
|
|
||||||
|
#if PY_MAJOR_VERSION < 3
|
||||||
|
Py_NoSiteFlag=1;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PY_MAJOR_VERSION < 3
|
||||||
|
Py_SetProgramName("android_python");
|
||||||
|
#else
|
||||||
Py_SetProgramName(L"android_python");
|
Py_SetProgramName(L"android_python");
|
||||||
|
#endif
|
||||||
|
|
||||||
#if PY_MAJOR_VERSION >= 3
|
#if PY_MAJOR_VERSION >= 3
|
||||||
/* our logging module for android
|
/* our logging module for android
|
||||||
|
@ -103,34 +166,55 @@ int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
LOGP("Preparing to initialize python");
|
LOGP("Preparing to initialize python");
|
||||||
|
|
||||||
if (dir_exists("crystax_python/")) {
|
// Set up the python path
|
||||||
LOGP("crystax_python exists");
|
|
||||||
char paths[256];
|
char paths[256];
|
||||||
|
|
||||||
|
char crystax_python_dir[256];
|
||||||
|
snprintf(crystax_python_dir, 256,
|
||||||
|
"%s/crystax_python", getenv("ANDROID_UNPACK"));
|
||||||
|
char python_bundle_dir[256];
|
||||||
|
snprintf(python_bundle_dir, 256,
|
||||||
|
"%s/_python_bundle", getenv("ANDROID_UNPACK"));
|
||||||
|
if (dir_exists(crystax_python_dir) || dir_exists(python_bundle_dir)) {
|
||||||
|
if (dir_exists(crystax_python_dir)) {
|
||||||
|
LOGP("crystax_python exists");
|
||||||
snprintf(paths, 256,
|
snprintf(paths, 256,
|
||||||
"%s/crystax_python/stdlib.zip:%s/crystax_python/modules",
|
"%s/stdlib.zip:%s/modules",
|
||||||
env_argument, env_argument);
|
crystax_python_dir, crystax_python_dir);
|
||||||
/* snprintf(paths, 256, "%s/stdlib.zip:%s/modules", env_argument,
|
}
|
||||||
* env_argument); */
|
|
||||||
|
if (dir_exists(python_bundle_dir)) {
|
||||||
|
LOGP("_python_bundle dir exists");
|
||||||
|
snprintf(paths, 256,
|
||||||
|
"%s/stdlib.zip:%s/modules",
|
||||||
|
python_bundle_dir, python_bundle_dir);
|
||||||
|
}
|
||||||
|
|
||||||
LOGP("calculated paths to be...");
|
LOGP("calculated paths to be...");
|
||||||
LOGP(paths);
|
LOGP(paths);
|
||||||
|
|
||||||
#if PY_MAJOR_VERSION >= 3
|
#if PY_MAJOR_VERSION >= 3
|
||||||
wchar_t *wchar_paths = Py_DecodeLocale(paths, NULL);
|
wchar_t *wchar_paths = Py_DecodeLocale(paths, NULL);
|
||||||
Py_SetPath(wchar_paths);
|
Py_SetPath(wchar_paths);
|
||||||
#else
|
|
||||||
char *wchar_paths = paths;
|
|
||||||
LOGP("Can't Py_SetPath in python2, so crystax python2 doesn't work yet");
|
|
||||||
exit(1);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
LOGP("set wchar paths...");
|
LOGP("set wchar paths...");
|
||||||
} else {
|
} else {
|
||||||
LOGP("crystax_python does not exist");
|
// We do not expect to see crystax_python any more, so no point
|
||||||
|
// reminding the user about it. If it does exist, we'll have
|
||||||
|
// logged it earlier.
|
||||||
|
LOGP("_python_bundle does not exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
Py_Initialize();
|
Py_Initialize();
|
||||||
|
|
||||||
#if PY_MAJOR_VERSION < 3
|
#if PY_MAJOR_VERSION < 3
|
||||||
|
// Can't Py_SetPath in python2 but we can set PySys_SetPath, which must
|
||||||
|
// be applied after Py_Initialize rather than before like Py_SetPath
|
||||||
|
#if PY_MICRO_VERSION >= 15
|
||||||
|
// Only for python native-build
|
||||||
|
PySys_SetPath(paths);
|
||||||
|
#endif
|
||||||
PySys_SetArgv(argc, argv);
|
PySys_SetArgv(argc, argv);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -153,8 +237,10 @@ int main(int argc, char *argv[]) {
|
||||||
*/
|
*/
|
||||||
PyRun_SimpleString("import sys, posix\n");
|
PyRun_SimpleString("import sys, posix\n");
|
||||||
if (dir_exists("lib")) {
|
if (dir_exists("lib")) {
|
||||||
/* If we built our own python, set up the paths correctly */
|
/* If we built our own python, set up the paths correctly.
|
||||||
LOGP("Setting up python from ANDROID_PRIVATE");
|
* This is only the case if we are using the python2legacy recipe
|
||||||
|
*/
|
||||||
|
LOGP("Setting up python from ANDROID_APP_PATH");
|
||||||
PyRun_SimpleString("private = posix.environ['ANDROID_APP_PATH']\n"
|
PyRun_SimpleString("private = posix.environ['ANDROID_APP_PATH']\n"
|
||||||
"argument = posix.environ['ANDROID_ARGUMENT']\n"
|
"argument = posix.environ['ANDROID_ARGUMENT']\n"
|
||||||
"sys.path[:] = [ \n"
|
"sys.path[:] = [ \n"
|
||||||
|
@ -165,11 +251,24 @@ int main(int argc, char *argv[]) {
|
||||||
" argument ]\n");
|
" argument ]\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dir_exists("crystax_python")) {
|
|
||||||
char add_site_packages_dir[256];
|
char add_site_packages_dir[256];
|
||||||
|
if (dir_exists(crystax_python_dir)) {
|
||||||
snprintf(add_site_packages_dir, 256,
|
snprintf(add_site_packages_dir, 256,
|
||||||
"sys.path.append('%s/crystax_python/site-packages')",
|
"sys.path.append('%s/site-packages')",
|
||||||
env_argument);
|
crystax_python_dir);
|
||||||
|
|
||||||
|
PyRun_SimpleString("import sys\n"
|
||||||
|
"sys.argv = ['notaninterpreterreally']\n"
|
||||||
|
"from os.path import realpath, join, dirname");
|
||||||
|
PyRun_SimpleString(add_site_packages_dir);
|
||||||
|
/* "sys.path.append(join(dirname(realpath(__file__)), 'site-packages'))") */
|
||||||
|
PyRun_SimpleString("sys.path = ['.'] + sys.path");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dir_exists(python_bundle_dir)) {
|
||||||
|
snprintf(add_site_packages_dir, 256,
|
||||||
|
"sys.path.append('%s/site-packages')",
|
||||||
|
python_bundle_dir);
|
||||||
|
|
||||||
PyRun_SimpleString("import sys\n"
|
PyRun_SimpleString("import sys\n"
|
||||||
"sys.argv = ['notaninterpreterreally']\n"
|
"sys.argv = ['notaninterpreterreally']\n"
|
||||||
|
@ -210,6 +309,11 @@ int main(int argc, char *argv[]) {
|
||||||
/* Get the entrypoint, search the .pyo then .py
|
/* Get the entrypoint, search the .pyo then .py
|
||||||
*/
|
*/
|
||||||
char *dot = strrchr(env_entrypoint, '.');
|
char *dot = strrchr(env_entrypoint, '.');
|
||||||
|
#if PY_MAJOR_VERSION > 2
|
||||||
|
char *ext = ".pyc";
|
||||||
|
#else
|
||||||
|
char *ext = ".pyo";
|
||||||
|
#endif
|
||||||
if (dot <= 0) {
|
if (dot <= 0) {
|
||||||
LOGP("Invalid entrypoint, abort.");
|
LOGP("Invalid entrypoint, abort.");
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -218,14 +322,14 @@ int main(int argc, char *argv[]) {
|
||||||
LOGP("Entrypoint path is too long, try increasing ENTRYPOINT_MAXLEN.");
|
LOGP("Entrypoint path is too long, try increasing ENTRYPOINT_MAXLEN.");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (!strcmp(dot, ".pyo")) {
|
if (!strcmp(dot, ext)) {
|
||||||
if (!file_exists(env_entrypoint)) {
|
if (!file_exists(env_entrypoint)) {
|
||||||
/* fallback on .py */
|
/* fallback on .py */
|
||||||
strcpy(entrypoint, env_entrypoint);
|
strcpy(entrypoint, env_entrypoint);
|
||||||
entrypoint[strlen(env_entrypoint) - 1] = '\0';
|
entrypoint[strlen(env_entrypoint) - 1] = '\0';
|
||||||
LOGP(entrypoint);
|
LOGP(entrypoint);
|
||||||
if (!file_exists(entrypoint)) {
|
if (!file_exists(entrypoint)) {
|
||||||
LOGP("Entrypoint not found (.pyo, fallback on .py), abort");
|
LOGP("Entrypoint not found (.pyc/.pyo, fallback on .py), abort");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -235,7 +339,11 @@ int main(int argc, char *argv[]) {
|
||||||
/* if .py is passed, check the pyo version first */
|
/* if .py is passed, check the pyo version first */
|
||||||
strcpy(entrypoint, env_entrypoint);
|
strcpy(entrypoint, env_entrypoint);
|
||||||
entrypoint[strlen(env_entrypoint) + 1] = '\0';
|
entrypoint[strlen(env_entrypoint) + 1] = '\0';
|
||||||
|
#if PY_MAJOR_VERSION > 2
|
||||||
|
entrypoint[strlen(env_entrypoint)] = 'c';
|
||||||
|
#else
|
||||||
entrypoint[strlen(env_entrypoint)] = 'o';
|
entrypoint[strlen(env_entrypoint)] = 'o';
|
||||||
|
#endif
|
||||||
if (!file_exists(entrypoint)) {
|
if (!file_exists(entrypoint)) {
|
||||||
/* fallback on pure python version */
|
/* fallback on pure python version */
|
||||||
if (!file_exists(env_entrypoint)) {
|
if (!file_exists(env_entrypoint)) {
|
||||||
|
@ -245,7 +353,7 @@ int main(int argc, char *argv[]) {
|
||||||
strcpy(entrypoint, env_entrypoint);
|
strcpy(entrypoint, env_entrypoint);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOGP("Entrypoint have an invalid extension (must be .py or .pyo), abort.");
|
LOGP("Entrypoint have an invalid extension (must be .py or .pyc/.pyo), abort.");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
// LOGP("Entrypoint is:");
|
// LOGP("Entrypoint is:");
|
||||||
|
@ -260,6 +368,7 @@ int main(int argc, char *argv[]) {
|
||||||
/* run python !
|
/* run python !
|
||||||
*/
|
*/
|
||||||
ret = PyRun_SimpleFile(fd, entrypoint);
|
ret = PyRun_SimpleFile(fd, entrypoint);
|
||||||
|
fclose(fd);
|
||||||
|
|
||||||
if (PyErr_Occurred() != NULL) {
|
if (PyErr_Occurred() != NULL) {
|
||||||
ret = 1;
|
ret = 1;
|
||||||
|
@ -270,19 +379,48 @@ int main(int argc, char *argv[]) {
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* close everything
|
|
||||||
*/
|
|
||||||
Py_Finalize();
|
|
||||||
fclose(fd);
|
|
||||||
|
|
||||||
LOGP("Python for android ended.");
|
LOGP("Python for android ended.");
|
||||||
|
|
||||||
|
/* Shut down: since regular shutdown causes issues sometimes
|
||||||
|
(seems to be an incomplete shutdown breaking next launch)
|
||||||
|
we'll use sys.exit(ret) to shutdown, since that one works.
|
||||||
|
|
||||||
|
Reference discussion:
|
||||||
|
|
||||||
|
https://github.com/kivy/kivy/pull/6107#issue-246120816
|
||||||
|
*/
|
||||||
|
char terminatecmd[256];
|
||||||
|
snprintf(
|
||||||
|
terminatecmd, sizeof(terminatecmd),
|
||||||
|
"import sys; sys.exit(%d)\n", ret
|
||||||
|
);
|
||||||
|
PyRun_SimpleString(terminatecmd);
|
||||||
|
|
||||||
|
/* This should never actually be reached, but we'll leave the clean-up
|
||||||
|
* here just to be safe.
|
||||||
|
*/
|
||||||
|
#if PY_MAJOR_VERSION < 3
|
||||||
|
Py_Finalize();
|
||||||
|
LOGP("Unexpectedly reached Py_FinalizeEx(), but was successful.");
|
||||||
|
#else
|
||||||
|
if (Py_FinalizeEx() != 0) // properly check success on Python 3
|
||||||
|
LOGP("Unexpectedly reached Py_FinalizeEx(), and got error!");
|
||||||
|
else
|
||||||
|
LOGP("Unexpectedly reached Py_FinalizeEx(), but was successful.");
|
||||||
|
#endif
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_org_kivy_android_PythonService_nativeStart(
|
JNIEXPORT void JNICALL Java_org_kivy_android_PythonService_nativeStart(
|
||||||
JNIEnv *env, jobject thiz, jstring j_android_private,
|
JNIEnv *env,
|
||||||
jstring j_android_argument, jstring j_service_entrypoint,
|
jobject thiz,
|
||||||
jstring j_python_name, jstring j_python_home, jstring j_python_path,
|
jstring j_android_private,
|
||||||
|
jstring j_android_argument,
|
||||||
|
jstring j_service_entrypoint,
|
||||||
|
jstring j_python_name,
|
||||||
|
jstring j_python_home,
|
||||||
|
jstring j_python_path,
|
||||||
jstring j_arg) {
|
jstring j_arg) {
|
||||||
jboolean iscopy;
|
jboolean iscopy;
|
||||||
const char *android_private =
|
const char *android_private =
|
||||||
|
@ -308,10 +446,7 @@ JNIEXPORT void JNICALL Java_org_kivy_android_PythonService_nativeStart(
|
||||||
setenv("PYTHONHOME", python_home, 1);
|
setenv("PYTHONHOME", python_home, 1);
|
||||||
setenv("PYTHONPATH", python_path, 1);
|
setenv("PYTHONPATH", python_path, 1);
|
||||||
setenv("PYTHON_SERVICE_ARGUMENT", arg, 1);
|
setenv("PYTHON_SERVICE_ARGUMENT", arg, 1);
|
||||||
|
setenv("P4A_BOOTSTRAP", bootstrap_name, 1);
|
||||||
char ca_path[128];
|
|
||||||
snprintf(ca_path, 128, "%s/crystax_python/site-packages/certifi/cacert.pem", python_home);
|
|
||||||
setenv("SSL_CERT_FILE", ca_path, 1);
|
|
||||||
|
|
||||||
char *argv[] = {"."};
|
char *argv[] = {"."};
|
||||||
/* ANDROID_ARGUMENT points to service subdir,
|
/* ANDROID_ARGUMENT points to service subdir,
|
||||||
|
@ -320,4 +455,47 @@ JNIEXPORT void JNICALL Java_org_kivy_android_PythonService_nativeStart(
|
||||||
main(1, argv);
|
main(1, argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(BOOTSTRAP_NAME_WEBVIEW) || defined(BOOTSTRAP_NAME_SERVICEONLY)
|
||||||
|
// Webview and service_only uses some more functions:
|
||||||
|
|
||||||
|
void Java_org_kivy_android_PythonActivity_nativeSetenv(
|
||||||
|
JNIEnv* env, jclass cls,
|
||||||
|
jstring name, jstring value)
|
||||||
|
//JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)(
|
||||||
|
// JNIEnv* env, jclass cls,
|
||||||
|
// jstring name, jstring value)
|
||||||
|
{
|
||||||
|
const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
|
||||||
|
const char *utfvalue = (*env)->GetStringUTFChars(env, value, NULL);
|
||||||
|
|
||||||
|
setenv(utfname, utfvalue, 1);
|
||||||
|
|
||||||
|
(*env)->ReleaseStringUTFChars(env, name, utfname);
|
||||||
|
(*env)->ReleaseStringUTFChars(env, value, utfvalue);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Java_org_kivy_android_PythonActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj)
|
||||||
|
{
|
||||||
|
/* This nativeInit follows SDL2 */
|
||||||
|
|
||||||
|
/* This interface could expand with ABI negotiation, calbacks, etc. */
|
||||||
|
/* SDL_Android_Init(env, cls); */
|
||||||
|
|
||||||
|
/* SDL_SetMainReady(); */
|
||||||
|
|
||||||
|
/* Run the application code! */
|
||||||
|
int status;
|
||||||
|
char *argv[2];
|
||||||
|
argv[0] = "Python_app";
|
||||||
|
argv[1] = NULL;
|
||||||
|
/* status = SDL_main(1, argv); */
|
||||||
|
|
||||||
|
main(1, argv);
|
||||||
|
|
||||||
|
/* Do not issue an exit or the whole application will terminate instead of just the SDL thread */
|
||||||
|
/* exit(status); */
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
|
@ -0,0 +1,164 @@
|
||||||
|
package org.kivy.android;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.os.Process;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import org.kivy.android.PythonUtil;
|
||||||
|
|
||||||
|
import org.renpy.android.Hardware;
|
||||||
|
|
||||||
|
|
||||||
|
public class PythonService extends Service implements Runnable {
|
||||||
|
|
||||||
|
// Thread for Python code
|
||||||
|
private Thread pythonThread = null;
|
||||||
|
|
||||||
|
// Python environment variables
|
||||||
|
private String androidPrivate;
|
||||||
|
private String androidArgument;
|
||||||
|
private String pythonName;
|
||||||
|
private String pythonHome;
|
||||||
|
private String pythonPath;
|
||||||
|
private String serviceEntrypoint;
|
||||||
|
// Argument to pass to Python code,
|
||||||
|
private String pythonServiceArgument;
|
||||||
|
public static PythonService mService = null;
|
||||||
|
private Intent startIntent = null;
|
||||||
|
|
||||||
|
private boolean autoRestartService = false;
|
||||||
|
|
||||||
|
public void setAutoRestartService(boolean restart) {
|
||||||
|
autoRestartService = restart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canDisplayNotification() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int startType() {
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent arg0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
if (pythonThread != null) {
|
||||||
|
Log.v("python service", "service exists, do not start again");
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
startIntent = intent;
|
||||||
|
Bundle extras = intent.getExtras();
|
||||||
|
androidPrivate = extras.getString("androidPrivate");
|
||||||
|
androidArgument = extras.getString("androidArgument");
|
||||||
|
serviceEntrypoint = extras.getString("serviceEntrypoint");
|
||||||
|
pythonName = extras.getString("pythonName");
|
||||||
|
pythonHome = extras.getString("pythonHome");
|
||||||
|
pythonPath = extras.getString("pythonPath");
|
||||||
|
pythonServiceArgument = extras.getString("pythonServiceArgument");
|
||||||
|
|
||||||
|
pythonThread = new Thread(this);
|
||||||
|
pythonThread.start();
|
||||||
|
|
||||||
|
if (canDisplayNotification()) {
|
||||||
|
doStartForeground(extras);
|
||||||
|
}
|
||||||
|
|
||||||
|
return startType();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doStartForeground(Bundle extras) {
|
||||||
|
String serviceTitle = extras.getString("serviceTitle");
|
||||||
|
String serviceDescription = extras.getString("serviceDescription");
|
||||||
|
|
||||||
|
Notification notification;
|
||||||
|
Context context = getApplicationContext();
|
||||||
|
Intent contextIntent = new Intent(context, PythonActivity.class);
|
||||||
|
PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
||||||
|
notification = new Notification(
|
||||||
|
context.getApplicationInfo().icon, serviceTitle, System.currentTimeMillis());
|
||||||
|
try {
|
||||||
|
// prevent using NotificationCompat, this saves 100kb on apk
|
||||||
|
Method func = notification.getClass().getMethod(
|
||||||
|
"setLatestEventInfo", Context.class, CharSequence.class,
|
||||||
|
CharSequence.class, PendingIntent.class);
|
||||||
|
func.invoke(notification, context, serviceTitle, serviceDescription, pIntent);
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException |
|
||||||
|
IllegalArgumentException | InvocationTargetException e) {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Notification.Builder builder = new Notification.Builder(context);
|
||||||
|
builder.setContentTitle(serviceTitle);
|
||||||
|
builder.setContentText(serviceDescription);
|
||||||
|
builder.setContentIntent(pIntent);
|
||||||
|
builder.setSmallIcon(context.getApplicationInfo().icon);
|
||||||
|
notification = builder.build();
|
||||||
|
}
|
||||||
|
startForeground(1, notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
pythonThread = null;
|
||||||
|
if (autoRestartService && startIntent != null) {
|
||||||
|
Log.v("python service", "service restart requested");
|
||||||
|
startService(startIntent);
|
||||||
|
}
|
||||||
|
Process.killProcess(Process.myPid());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the task gracefully when killed.
|
||||||
|
* Calling stopSelf() will trigger a onDestroy() call from the system.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onTaskRemoved(Intent rootIntent) {
|
||||||
|
super.onTaskRemoved(rootIntent);
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(){
|
||||||
|
String app_root = getFilesDir().getAbsolutePath() + "/app";
|
||||||
|
File app_root_file = new File(app_root);
|
||||||
|
PythonUtil.loadLibraries(app_root_file,
|
||||||
|
new File(getApplicationInfo().nativeLibraryDir));
|
||||||
|
this.mService = this;
|
||||||
|
nativeStart(
|
||||||
|
androidPrivate, androidArgument,
|
||||||
|
serviceEntrypoint, pythonName,
|
||||||
|
pythonHome, pythonPath,
|
||||||
|
pythonServiceArgument);
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Native part
|
||||||
|
public static native void nativeStart(
|
||||||
|
String androidPrivate, String androidArgument,
|
||||||
|
String serviceEntrypoint, String pythonName,
|
||||||
|
String pythonHome, String pythonPath,
|
||||||
|
String pythonServiceArgument);
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package org.kivy.android;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.io.FilenameFilter;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
|
||||||
|
public class PythonUtil {
|
||||||
|
private static final String TAG = "pythonutil";
|
||||||
|
|
||||||
|
protected static void addLibraryIfExists(ArrayList<String> libsList, String pattern, File libsDir) {
|
||||||
|
// pattern should be the name of the lib file, without the
|
||||||
|
// preceding "lib" or suffix ".so", for instance "ssl.*" will
|
||||||
|
// match files of the form "libssl.*.so".
|
||||||
|
File [] files = libsDir.listFiles();
|
||||||
|
|
||||||
|
pattern = "lib" + pattern + "\\.so";
|
||||||
|
Pattern p = Pattern.compile(pattern);
|
||||||
|
for (int i = 0; i < files.length; ++i) {
|
||||||
|
File file = files[i];
|
||||||
|
String name = file.getName();
|
||||||
|
Log.v(TAG, "Checking pattern " + pattern + " against " + name);
|
||||||
|
if (p.matcher(name).matches()) {
|
||||||
|
Log.v(TAG, "Pattern " + pattern + " matched file " + name);
|
||||||
|
libsList.add(name.substring(3, name.length() - 3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static ArrayList<String> getLibraries(File libsDir) {
|
||||||
|
ArrayList<String> libsList = new ArrayList<String>();
|
||||||
|
addLibraryIfExists(libsList, "crystax", libsDir);
|
||||||
|
addLibraryIfExists(libsList, "sqlite3", libsDir);
|
||||||
|
addLibraryIfExists(libsList, "ffi", libsDir);
|
||||||
|
addLibraryIfExists(libsList, "ssl.*", libsDir);
|
||||||
|
addLibraryIfExists(libsList, "crypto.*", libsDir);
|
||||||
|
libsList.add("python2.7");
|
||||||
|
libsList.add("python3.5m");
|
||||||
|
libsList.add("python3.6m");
|
||||||
|
libsList.add("python3.7m");
|
||||||
|
libsList.add("main");
|
||||||
|
return libsList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadLibraries(File filesDir, File libsDir) {
|
||||||
|
String filesDirPath = filesDir.getAbsolutePath();
|
||||||
|
boolean foundPython = false;
|
||||||
|
|
||||||
|
for (String lib : getLibraries(libsDir)) {
|
||||||
|
Log.v(TAG, "Loading library: " + lib);
|
||||||
|
try {
|
||||||
|
System.loadLibrary(lib);
|
||||||
|
if (lib.startsWith("python")) {
|
||||||
|
foundPython = true;
|
||||||
|
}
|
||||||
|
} catch(UnsatisfiedLinkError e) {
|
||||||
|
// If this is the last possible libpython
|
||||||
|
// load, and it has failed, give a more
|
||||||
|
// general error
|
||||||
|
Log.v(TAG, "Library loading error: " + e.getMessage());
|
||||||
|
if (lib.startsWith("python3.7") && !foundPython) {
|
||||||
|
throw new java.lang.RuntimeException("Could not load any libpythonXXX.so");
|
||||||
|
} else if (lib.startsWith("python")) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
Log.v(TAG, "An UnsatisfiedLinkError occurred loading " + lib);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.v(TAG, "Loaded everything!");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
// This string is autogenerated by ChangeAppSettings.sh, do not change
|
||||||
|
// spaces amount
|
||||||
|
package org.renpy.android;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
import android.content.res.AssetManager;
|
||||||
|
|
||||||
|
import org.kamranzafar.jtar.*;
|
||||||
|
|
||||||
|
public class AssetExtract {
|
||||||
|
|
||||||
|
private AssetManager mAssetManager = null;
|
||||||
|
private Activity mActivity = null;
|
||||||
|
|
||||||
|
public AssetExtract(Activity act) {
|
||||||
|
mActivity = act;
|
||||||
|
mAssetManager = act.getAssets();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean extractTar(String asset, String target) {
|
||||||
|
|
||||||
|
byte buf[] = new byte[1024 * 1024];
|
||||||
|
|
||||||
|
InputStream assetStream = null;
|
||||||
|
TarInputStream tis = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
assetStream = mAssetManager.open(asset, AssetManager.ACCESS_STREAMING);
|
||||||
|
tis = new TarInputStream(new BufferedInputStream(new GZIPInputStream(new BufferedInputStream(assetStream, 8192)), 8192));
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e("python", "opening up extract tar", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
TarEntry entry = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
entry = tis.getNextEntry();
|
||||||
|
} catch ( java.io.IOException e ) {
|
||||||
|
Log.e("python", "extracting tar", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( entry == null ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.v("python", "extracting " + entry.getName());
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
new File(target +"/" + entry.getName()).mkdirs();
|
||||||
|
} catch ( SecurityException e ) { };
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputStream out = null;
|
||||||
|
String path = target + "/" + entry.getName();
|
||||||
|
|
||||||
|
try {
|
||||||
|
out = new BufferedOutputStream(new FileOutputStream(path), 8192);
|
||||||
|
} catch ( FileNotFoundException e ) {
|
||||||
|
} catch ( SecurityException e ) { };
|
||||||
|
|
||||||
|
if ( out == null ) {
|
||||||
|
Log.e("python", "could not open " + path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
int len = tis.read(buf);
|
||||||
|
|
||||||
|
if (len == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.write(buf, 0, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.flush();
|
||||||
|
out.close();
|
||||||
|
} catch ( java.io.IOException e ) {
|
||||||
|
Log.e("python", "extracting zip", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
tis.close();
|
||||||
|
assetStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/**
|
||||||
|
* This class takes care of managing resources for us. In our code, we
|
||||||
|
* can't use R, since the name of the package containing R will
|
||||||
|
* change. (This same code is used in both org.renpy.android and
|
||||||
|
* org.renpy.pygame.) So this is the next best thing.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.renpy.android;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
public class ResourceManager {
|
||||||
|
|
||||||
|
private Activity act;
|
||||||
|
private Resources res;
|
||||||
|
|
||||||
|
public ResourceManager(Activity activity) {
|
||||||
|
act = activity;
|
||||||
|
res = act.getResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIdentifier(String name, String kind) {
|
||||||
|
Log.v("SDL", "getting identifier");
|
||||||
|
Log.v("SDL", "kind is " + kind + " and name " + name);
|
||||||
|
Log.v("SDL", "result is " + res.getIdentifier(name, kind, act.getPackageName()));
|
||||||
|
return res.getIdentifier(name, kind, act.getPackageName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString(String name) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
Log.v("SDL", "asked to get string " + name);
|
||||||
|
return res.getString(getIdentifier(name, "string"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.v("SDL", "got exception looking for string!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public View inflateView(String name) {
|
||||||
|
int id = getIdentifier(name, "layout");
|
||||||
|
return act.getLayoutInflater().inflate(id, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public View getViewById(View v, String name) {
|
||||||
|
int id = getIdentifier(name, "id");
|
||||||
|
return v.findViewById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package {{ args.package }};
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import org.kivy.android.PythonService;
|
||||||
|
import org.kivy.android.PythonActivity;
|
||||||
|
|
||||||
|
|
||||||
|
public class Service{{ name|capitalize }} extends PythonService {
|
||||||
|
{% if sticky %}
|
||||||
|
@Override
|
||||||
|
public int startType() {
|
||||||
|
return START_STICKY;
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if not foreground %}
|
||||||
|
@Override
|
||||||
|
public boolean canDisplayNotification() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doStartForeground(Bundle extras) {
|
||||||
|
Notification notification;
|
||||||
|
Context context = getApplicationContext();
|
||||||
|
Intent contextIntent = new Intent(context, PythonActivity.class);
|
||||||
|
PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
||||||
|
notification = new Notification(
|
||||||
|
context.getApplicationInfo().icon, "{{ args.name }}", System.currentTimeMillis());
|
||||||
|
try {
|
||||||
|
// prevent using NotificationCompat, this saves 100kb on apk
|
||||||
|
Method func = notification.getClass().getMethod(
|
||||||
|
"setLatestEventInfo", Context.class, CharSequence.class,
|
||||||
|
CharSequence.class, PendingIntent.class);
|
||||||
|
func.invoke(notification, context, "{{ args.name }}", "{{ name| capitalize }}", pIntent);
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException |
|
||||||
|
IllegalArgumentException | InvocationTargetException e) {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Notification.Builder builder = new Notification.Builder(context);
|
||||||
|
builder.setContentTitle("{{ args.name }}");
|
||||||
|
builder.setContentText("{{ name| capitalize }}");
|
||||||
|
builder.setContentIntent(pIntent);
|
||||||
|
builder.setSmallIcon(context.getApplicationInfo().icon);
|
||||||
|
notification = builder.build();
|
||||||
|
}
|
||||||
|
startForeground({{ service_id }}, notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public void start(Context ctx, String pythonServiceArgument) {
|
||||||
|
Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
|
||||||
|
String argument = ctx.getFilesDir().getAbsolutePath() + "/app";
|
||||||
|
intent.putExtra("androidPrivate", ctx.getFilesDir().getAbsolutePath());
|
||||||
|
intent.putExtra("androidArgument", argument);
|
||||||
|
intent.putExtra("serviceEntrypoint", "{{ entrypoint }}");
|
||||||
|
intent.putExtra("pythonName", "{{ name }}");
|
||||||
|
intent.putExtra("pythonHome", argument);
|
||||||
|
intent.putExtra("pythonPath", argument + ":" + argument + "/lib");
|
||||||
|
intent.putExtra("pythonServiceArgument", pythonServiceArgument);
|
||||||
|
ctx.startService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public void stop(Context ctx) {
|
||||||
|
Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
|
||||||
|
ctx.stopService(intent);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
# This file is used to override default values used by the Ant build system.
|
||||||
|
#
|
||||||
|
# This file must be checked in Version Control Systems, as it is
|
||||||
|
# integral to the build system of your project.
|
||||||
|
|
||||||
|
# This file is only used by the Ant script.
|
||||||
|
|
||||||
|
# You can use this to override default values such as
|
||||||
|
# 'source.dir' for the location of your java source folder and
|
||||||
|
# 'out.dir' for the location of your output folder.
|
||||||
|
|
||||||
|
# You can also use it define how the release builds are signed by declaring
|
||||||
|
# the following properties:
|
||||||
|
# 'key.store' for the location of your keystore and
|
||||||
|
# 'key.alias' for the name of the key to use.
|
||||||
|
# The password will be asked during the build when you use the 'release' target.
|
||||||
|
|
||||||
|
key.store=${env.P4A_RELEASE_KEYSTORE}
|
||||||
|
key.alias=${env.P4A_RELEASE_KEYALIAS}
|
||||||
|
key.store.password=${env.P4A_RELEASE_KEYSTORE_PASSWD}
|
||||||
|
key.alias.password=${env.P4A_RELEASE_KEYALIAS_PASSWD}
|
|
@ -0,0 +1,80 @@
|
||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:3.1.4'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
flatDir {
|
||||||
|
dirs 'libs'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion {{ android_api }}
|
||||||
|
buildToolsVersion '{{ build_tools_version }}'
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion {{ args.min_sdk_version }}
|
||||||
|
targetSdkVersion {{ android_api }}
|
||||||
|
versionCode {{ args.numeric_version }}
|
||||||
|
versionName '{{ args.version }}'
|
||||||
|
}
|
||||||
|
|
||||||
|
{% if args.sign -%}
|
||||||
|
signingConfigs {
|
||||||
|
release {
|
||||||
|
storeFile file(System.getenv("P4A_RELEASE_KEYSTORE"))
|
||||||
|
keyAlias System.getenv("P4A_RELEASE_KEYALIAS")
|
||||||
|
storePassword System.getenv("P4A_RELEASE_KEYSTORE_PASSWD")
|
||||||
|
keyPassword System.getenv("P4A_RELEASE_KEYALIAS_PASSWD")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
debug {
|
||||||
|
}
|
||||||
|
release {
|
||||||
|
{% if args.sign -%}
|
||||||
|
signingConfig signingConfigs.release
|
||||||
|
{%- endif %}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_7
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_7
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
jniLibs.srcDir 'libs'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
{%- for aar in aars %}
|
||||||
|
compile(name: '{{ aar }}', ext: 'aar')
|
||||||
|
{%- endfor -%}
|
||||||
|
{%- for jar in jars %}
|
||||||
|
compile files('src/main/libs/{{ jar }}')
|
||||||
|
{%- endfor -%}
|
||||||
|
{%- if args.depends -%}
|
||||||
|
{%- for depend in args.depends %}
|
||||||
|
compile '{{ depend }}'
|
||||||
|
{%- endfor %}
|
||||||
|
{%- endif %}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- This should be changed to the name of your project -->
|
||||||
|
<project name="{{ versioned_name }}" default="help">
|
||||||
|
|
||||||
|
<!-- The local.properties file is created and updated by the 'android' tool.
|
||||||
|
It contains the path to the SDK. It should *NOT* be checked into
|
||||||
|
Version Control Systems. -->
|
||||||
|
<property file="local.properties" />
|
||||||
|
|
||||||
|
<!-- The ant.properties file can be created by you. It is only edited by the
|
||||||
|
'android' tool to add properties to it.
|
||||||
|
This is the place to change some Ant specific build properties.
|
||||||
|
Here are some properties you may want to change/update:
|
||||||
|
|
||||||
|
source.dir
|
||||||
|
The name of the source directory. Default is 'src'.
|
||||||
|
out.dir
|
||||||
|
The name of the output directory. Default is 'bin'.
|
||||||
|
|
||||||
|
For other overridable properties, look at the beginning of the rules
|
||||||
|
files in the SDK, at tools/ant/build.xml
|
||||||
|
|
||||||
|
Properties related to the SDK location or the project target should
|
||||||
|
be updated using the 'android' tool with the 'update' action.
|
||||||
|
|
||||||
|
This file is an integral part of the build system for your
|
||||||
|
application and should be checked into Version Control Systems.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<property file="ant.properties" />
|
||||||
|
|
||||||
|
<!-- if sdk.dir was not set from one of the property file, then
|
||||||
|
get it from the ANDROID_HOME env var.
|
||||||
|
This must be done before we load project.properties since
|
||||||
|
the proguard config can use sdk.dir -->
|
||||||
|
<property environment="env" />
|
||||||
|
<condition property="sdk.dir" value="${env.ANDROID_HOME}">
|
||||||
|
<isset property="env.ANDROID_HOME" />
|
||||||
|
</condition>
|
||||||
|
|
||||||
|
<property file="build.properties" />
|
||||||
|
|
||||||
|
<!-- The project.properties file is created and updated by the 'android'
|
||||||
|
tool, as well as ADT.
|
||||||
|
|
||||||
|
This contains project specific properties such as project target, and library
|
||||||
|
dependencies. Lower level build properties are stored in ant.properties
|
||||||
|
(or in .classpath for Eclipse projects).
|
||||||
|
|
||||||
|
This file is an integral part of the build system for your
|
||||||
|
application and should be checked into Version Control Systems. -->
|
||||||
|
<loadproperties srcFile="project.properties" />
|
||||||
|
|
||||||
|
<!-- quick check on sdk.dir -->
|
||||||
|
<fail
|
||||||
|
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
|
||||||
|
unless="sdk.dir"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Import per project custom build rules if present at the root of the project.
|
||||||
|
This is the place to put custom intermediary targets such as:
|
||||||
|
-pre-build
|
||||||
|
-pre-compile
|
||||||
|
-post-compile (This is typically used for code obfuscation.
|
||||||
|
Compiled code location: ${out.classes.absolute.dir}
|
||||||
|
If this is not done in place, override ${out.dex.input.absolute.dir})
|
||||||
|
-post-package
|
||||||
|
-post-build
|
||||||
|
-pre-clean
|
||||||
|
-->
|
||||||
|
<import file="custom_rules.xml" optional="true" />
|
||||||
|
|
||||||
|
<!-- Import the actual build file.
|
||||||
|
|
||||||
|
To customize existing targets, there are two options:
|
||||||
|
- Customize only one target:
|
||||||
|
- copy/paste the target into this file, *before* the
|
||||||
|
<import> task.
|
||||||
|
- customize it to your needs.
|
||||||
|
- Customize the whole content of build.xml
|
||||||
|
- copy/paste the content of the rules files (minus the top node)
|
||||||
|
into this file, replacing the <import> task.
|
||||||
|
- customize to your needs.
|
||||||
|
|
||||||
|
***********************
|
||||||
|
****** IMPORTANT ******
|
||||||
|
***********************
|
||||||
|
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
|
||||||
|
in order to avoid having your file be overridden by tools such as "android update project"
|
||||||
|
-->
|
||||||
|
<!-- version-tag: 1 -->
|
||||||
|
<import file="${sdk.dir}/tools/ant/build.xml" />
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project name="CustomRules">
|
||||||
|
<target name="-pre-build">
|
||||||
|
<copy todir="tmp-src">
|
||||||
|
{% if args.launcher %}
|
||||||
|
<fileset dir="src/main/java" includes="**" />
|
||||||
|
{% else %}
|
||||||
|
<fileset dir="src/main/java">
|
||||||
|
<exclude name="org/kivy/android/ProjectAdapter.java" />
|
||||||
|
<exclude name="org/kivy/android/ProjectChooser.java" />
|
||||||
|
</fileset>
|
||||||
|
{% endif %}
|
||||||
|
{% for dir, includes in args.extra_source_dirs %}
|
||||||
|
<fileset dir="{{ dir }}" includes="{{ includes }}" />
|
||||||
|
{% endfor %}
|
||||||
|
</copy>
|
||||||
|
</target>
|
||||||
|
<target name="-post-build">
|
||||||
|
<delete dir="tmp-src" />
|
||||||
|
</target>
|
||||||
|
</project>
|
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
|
@ -0,0 +1 @@
|
||||||
|
# put files here that you need to un-blacklist
|
|
@ -7,13 +7,13 @@ LOCAL_MODULE := main
|
||||||
# Add your application source files here...
|
# Add your application source files here...
|
||||||
LOCAL_SRC_FILES := start.c pyjniusjni.c
|
LOCAL_SRC_FILES := start.c pyjniusjni.c
|
||||||
|
|
||||||
LOCAL_CFLAGS += -I$(LOCAL_PATH)/../../../../other_builds/$(PYTHON2_NAME)/$(ARCH)/python2/python-install/include/python2.7 $(EXTRA_CFLAGS)
|
LOCAL_CFLAGS += -I$(PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS)
|
||||||
|
|
||||||
LOCAL_SHARED_LIBRARIES := python_shared
|
LOCAL_SHARED_LIBRARIES := python_shared
|
||||||
|
|
||||||
LOCAL_LDLIBS := -llog $(EXTRA_LDLIBS)
|
LOCAL_LDLIBS := -llog $(EXTRA_LDLIBS)
|
||||||
|
|
||||||
LOCAL_LDFLAGS += -L$(LOCAL_PATH)/../../../../other_builds/$(PYTHON2_NAME)/$(ARCH)/python2/python-install/lib $(APPLICATION_ADDITIONAL_LDFLAGS)
|
LOCAL_LDFLAGS += -L$(PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS)
|
||||||
|
|
||||||
include $(BUILD_SHARED_LIBRARY)
|
include $(BUILD_SHARED_LIBRARY)
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
#define BOOTSTRAP_NAME_SERVICEONLY
|
||||||
|
#define BOOTSTRAP_USES_NO_SDL_HEADERS
|
||||||
|
|
||||||
|
const char bootstrap_name[] = "service_only";
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
<!-- OpenGL ES 2.0 -->
|
<!-- OpenGL ES 2.0 -->
|
||||||
<uses-feature android:glEsVersion="0x00020000" />
|
<uses-feature android:glEsVersion="0x00020000" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
{% for perm in args.permissions %}
|
{% for perm in args.permissions %}
|
||||||
{% if '.' in perm %}
|
{% if '.' in perm %}
|
||||||
<uses-permission android:name="{{ perm }}" />
|
<uses-permission android:name="{{ perm }}" />
|
||||||
|
@ -54,7 +55,8 @@
|
||||||
android:icon="@drawable/icon"
|
android:icon="@drawable/icon"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:theme="@style/LbryAppTheme"
|
android:theme="@style/LbryAppTheme"
|
||||||
android:hardwareAccelerated="true">
|
android:hardwareAccelerated="true"
|
||||||
|
android:usesCleartextTraffic="true">
|
||||||
|
|
||||||
{% for m in args.meta_data %}
|
{% for m in args.meta_data %}
|
||||||
<meta-data android:name="{{ m.split('=', 1)[0] }}" android:value="{{ m.split('=', 1)[-1] }}"/>{% endfor %}
|
<meta-data android:name="{{ m.split('=', 1)[0] }}" android:value="{{ m.split('=', 1)[-1] }}"/>{% endfor %}
|
||||||
|
|
|
@ -41,12 +41,6 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationVariants.all { variant ->
|
|
||||||
variant.outputs.all {
|
|
||||||
outputFileName = "../" + outputFileName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dexOptions {
|
dexOptions {
|
||||||
jumboMode true
|
jumboMode true
|
||||||
}
|
}
|
||||||
|
@ -98,10 +92,12 @@ subprojects {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
compile project(':@react-native-community_async-storage')
|
||||||
compile project(':react-native-exception-handler')
|
compile project(':react-native-exception-handler')
|
||||||
compile project(':react-native-fast-image')
|
compile project(':react-native-fast-image')
|
||||||
compile project(':rn-fetch-blob')
|
compile project(':react-native-gesture-handler')
|
||||||
compile project(':react-native-video')
|
compile project(':react-native-video')
|
||||||
|
compile project(':rn-fetch-blob')
|
||||||
{%- for aar in aars %}
|
{%- for aar in aars %}
|
||||||
compile(name: '{{ aar }}', ext: 'aar')
|
compile(name: '{{ aar }}', ext: 'aar')
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
rootProject.name = 'browser'
|
rootProject.name = 'browser'
|
||||||
|
include ':@react-native-community_async-storage'
|
||||||
|
project(':@react-native-community_async-storage').projectDir = new File(rootProject.projectDir, './react/node_modules/@react-native-community/async-storage/android')
|
||||||
include ':react-native-exception-handler'
|
include ':react-native-exception-handler'
|
||||||
project(':react-native-exception-handler').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-exception-handler/android')
|
project(':react-native-exception-handler').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-exception-handler/android')
|
||||||
include ':react-native-fast-image'
|
include ':react-native-fast-image'
|
||||||
project(':react-native-fast-image').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-fast-image/android')
|
project(':react-native-fast-image').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-fast-image/android')
|
||||||
include ':rn-fetch-blob'
|
include ':react-native-gesture-handler'
|
||||||
project(':rn-fetch-blob').projectDir = new File(rootProject.projectDir, './react/node_modules/rn-fetch-blob/android')
|
project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-gesture-handler/android')
|
||||||
include ':react-native-video'
|
include ':react-native-video'
|
||||||
project(':react-native-video').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-video/android-exoplayer')
|
project(':react-native-video').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-video/android-exoplayer')
|
||||||
|
include ':rn-fetch-blob'
|
||||||
|
project(':rn-fetch-blob').projectDir = new File(rootProject.projectDir, './react/node_modules/rn-fetch-blob/android')
|
||||||
|
|
|
@ -2,21 +2,25 @@ from __future__ import print_function
|
||||||
|
|
||||||
from os.path import (join, realpath, dirname, expanduser, exists,
|
from os.path import (join, realpath, dirname, expanduser, exists,
|
||||||
split, isdir)
|
split, isdir)
|
||||||
from os import environ, listdir
|
from os import environ
|
||||||
|
import copy
|
||||||
import os
|
import os
|
||||||
import glob
|
import glob
|
||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
import sh
|
import sh
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from pythonforandroid.util import (ensure_dir, current_directory)
|
from pythonforandroid.util import (
|
||||||
from pythonforandroid.logger import (info, warning, error, info_notify,
|
current_directory, ensure_dir, get_virtualenv_executable,
|
||||||
Err_Fore, Err_Style, info_main,
|
BuildInterruptingException
|
||||||
shprint)
|
)
|
||||||
from pythonforandroid.archs import ArchARM, ArchARMv7_a, Archx86, Archx86_64, ArchAarch_64
|
from pythonforandroid.logger import (info, warning, info_notify, info_main, shprint)
|
||||||
from pythonforandroid.recipe import Recipe
|
from pythonforandroid.archs import ArchARM, ArchARMv7_a, ArchAarch_64, Archx86, Archx86_64
|
||||||
|
from pythonforandroid.recipe import CythonRecipe, Recipe
|
||||||
DEFAULT_ANDROID_API = 15
|
from pythonforandroid.recommendations import (
|
||||||
|
check_ndk_version, check_target_api, check_ndk_api,
|
||||||
|
RECOMMENDED_NDK_API, RECOMMENDED_TARGET_API)
|
||||||
|
|
||||||
|
|
||||||
class Context(object):
|
class Context(object):
|
||||||
|
@ -24,14 +28,19 @@ class Context(object):
|
||||||
will be instantiated and used to hold all the build state.'''
|
will be instantiated and used to hold all the build state.'''
|
||||||
|
|
||||||
env = environ.copy()
|
env = environ.copy()
|
||||||
root_dir = None # the filepath of toolchain.py
|
# the filepath of toolchain.py
|
||||||
storage_dir = None # the root dir where builds and dists will be stored
|
root_dir = None
|
||||||
|
# the root dir where builds and dists will be stored
|
||||||
|
storage_dir = None
|
||||||
|
|
||||||
build_dir = None # in which bootstraps are copied for building
|
# in which bootstraps are copied for building
|
||||||
# and recipes are built
|
# and recipes are built
|
||||||
dist_dir = None # the Android project folder where everything ends up
|
build_dir = None
|
||||||
libs_dir = None # where Android libs are cached after build but
|
# the Android project folder where everything ends up
|
||||||
# before being placed in dists
|
dist_dir = None
|
||||||
|
# where Android libs are cached after build
|
||||||
|
# but before being placed in dists
|
||||||
|
libs_dir = None
|
||||||
aars_dir = None
|
aars_dir = None
|
||||||
|
|
||||||
ccache = None # whether to use ccache
|
ccache = None # whether to use ccache
|
||||||
|
@ -121,17 +130,17 @@ class Context(object):
|
||||||
self._android_api = value
|
self._android_api = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ndk_ver(self):
|
def ndk_api(self):
|
||||||
'''The version of the NDK being used for compilation.'''
|
'''The API number compile against'''
|
||||||
if self._ndk_ver is None:
|
if self._ndk_api is None:
|
||||||
raise ValueError('Tried to access ndk_ver but it has not '
|
raise ValueError('Tried to access ndk_api but it has not '
|
||||||
'been set - this should not happen, something '
|
'been set - this should not happen, something '
|
||||||
'went wrong!')
|
'went wrong!')
|
||||||
return self._ndk_ver
|
return self._ndk_api
|
||||||
|
|
||||||
@ndk_ver.setter
|
@ndk_api.setter
|
||||||
def ndk_ver(self, value):
|
def ndk_api(self, value):
|
||||||
self._ndk_ver = value
|
self._ndk_api = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sdk_dir(self):
|
def sdk_dir(self):
|
||||||
|
@ -159,9 +168,11 @@ class Context(object):
|
||||||
def ndk_dir(self, value):
|
def ndk_dir(self, value):
|
||||||
self._ndk_dir = value
|
self._ndk_dir = value
|
||||||
|
|
||||||
def prepare_build_environment(self, user_sdk_dir, user_ndk_dir,
|
def prepare_build_environment(self,
|
||||||
user_android_api, user_android_min_api,
|
user_sdk_dir,
|
||||||
user_ndk_ver):
|
user_ndk_dir,
|
||||||
|
user_android_api,
|
||||||
|
user_ndk_api):
|
||||||
'''Checks that build dependencies exist and sets internal variables
|
'''Checks that build dependencies exist and sets internal variables
|
||||||
for the Android SDK etc.
|
for the Android SDK etc.
|
||||||
|
|
||||||
|
@ -180,12 +191,14 @@ class Context(object):
|
||||||
sdk_dir = None
|
sdk_dir = None
|
||||||
if user_sdk_dir:
|
if user_sdk_dir:
|
||||||
sdk_dir = user_sdk_dir
|
sdk_dir = user_sdk_dir
|
||||||
if sdk_dir is None: # This is the old P4A-specific var
|
# This is the old P4A-specific var
|
||||||
|
if sdk_dir is None:
|
||||||
sdk_dir = environ.get('ANDROIDSDK', None)
|
sdk_dir = environ.get('ANDROIDSDK', None)
|
||||||
if sdk_dir is None: # This seems used more conventionally
|
# This seems used more conventionally
|
||||||
|
if sdk_dir is None:
|
||||||
sdk_dir = environ.get('ANDROID_HOME', None)
|
sdk_dir = environ.get('ANDROID_HOME', None)
|
||||||
if sdk_dir is None: # Checks in the buildozer SDK dir, useful
|
# Checks in the buildozer SDK dir, useful for debug tests of p4a
|
||||||
# for debug tests of p4a
|
if sdk_dir is None:
|
||||||
possible_dirs = glob.glob(expanduser(join(
|
possible_dirs = glob.glob(expanduser(join(
|
||||||
'~', '.buildozer', 'android', 'platform', 'android-sdk-*')))
|
'~', '.buildozer', 'android', 'platform', 'android-sdk-*')))
|
||||||
possible_dirs = [d for d in possible_dirs if not
|
possible_dirs = [d for d in possible_dirs if not
|
||||||
|
@ -199,57 +212,25 @@ class Context(object):
|
||||||
'maintain your own SDK download.')
|
'maintain your own SDK download.')
|
||||||
sdk_dir = possible_dirs[0]
|
sdk_dir = possible_dirs[0]
|
||||||
if sdk_dir is None:
|
if sdk_dir is None:
|
||||||
warning('Android SDK dir was not specified, exiting.')
|
raise BuildInterruptingException('Android SDK dir was not specified, exiting.')
|
||||||
exit(1)
|
|
||||||
self.sdk_dir = realpath(sdk_dir)
|
self.sdk_dir = realpath(sdk_dir)
|
||||||
|
|
||||||
# Check what Android API we're using
|
# Check what Android API we're using
|
||||||
android_api = None
|
android_api = None
|
||||||
if user_android_api:
|
if user_android_api:
|
||||||
android_api = user_android_api
|
android_api = user_android_api
|
||||||
if android_api is not None:
|
info('Getting Android API version from user argument: {}'.format(android_api))
|
||||||
info('Getting Android API version from user argument')
|
elif 'ANDROIDAPI' in environ:
|
||||||
if android_api is None:
|
android_api = environ['ANDROIDAPI']
|
||||||
android_api = environ.get('ANDROIDAPI', None)
|
info('Found Android API target in $ANDROIDAPI: {}'.format(android_api))
|
||||||
if android_api is not None:
|
else:
|
||||||
info('Found Android API target in $ANDROIDAPI')
|
|
||||||
if android_api is None:
|
|
||||||
info('Android API target was not set manually, using '
|
info('Android API target was not set manually, using '
|
||||||
'the default of {}'.format(DEFAULT_ANDROID_API))
|
'the default of {}'.format(RECOMMENDED_TARGET_API))
|
||||||
android_api = DEFAULT_ANDROID_API
|
android_api = RECOMMENDED_TARGET_API
|
||||||
android_api = int(android_api)
|
android_api = int(android_api)
|
||||||
self.android_api = android_api
|
self.android_api = android_api
|
||||||
|
|
||||||
if self.android_api >= 21 and self.archs[0].arch == 'armeabi':
|
check_target_api(android_api, self.archs[0].arch)
|
||||||
error('Asked to build for armeabi architecture with API '
|
|
||||||
'{}, but API 21 or greater does not support armeabi'.format(
|
|
||||||
self.android_api))
|
|
||||||
error('You probably want to build with --arch=armeabi-v7a instead')
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
# try to determinate min_api
|
|
||||||
android_min_api = None
|
|
||||||
if user_android_min_api:
|
|
||||||
android_min_api = user_android_min_api
|
|
||||||
if android_min_api is not None:
|
|
||||||
info('Getting Minimum Android API version from user argument')
|
|
||||||
if android_min_api is None:
|
|
||||||
android_min_api = environ.get("ANDROIDMINAPI", None)
|
|
||||||
if android_min_api is not None:
|
|
||||||
info('Found Android minimum api in $ANDROIDMINAPI')
|
|
||||||
if android_min_api is None:
|
|
||||||
info('Minimum Android API was not set, using current Android API '
|
|
||||||
'{}'.format(android_api))
|
|
||||||
android_min_api = android_api
|
|
||||||
android_min_api = int(android_min_api)
|
|
||||||
self.android_min_api = android_min_api
|
|
||||||
|
|
||||||
info("Requested API {} (minimum {})".format(
|
|
||||||
self.android_api, self.android_min_api))
|
|
||||||
|
|
||||||
if self.android_min_api > android_api:
|
|
||||||
error('Android minimum api cannot be higher than Android api')
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
if exists(join(sdk_dir, 'tools', 'bin', 'avdmanager')):
|
if exists(join(sdk_dir, 'tools', 'bin', 'avdmanager')):
|
||||||
avdmanager = sh.Command(join(sdk_dir, 'tools', 'bin', 'avdmanager'))
|
avdmanager = sh.Command(join(sdk_dir, 'tools', 'bin', 'avdmanager'))
|
||||||
|
@ -258,9 +239,9 @@ class Context(object):
|
||||||
android = sh.Command(join(sdk_dir, 'tools', 'android'))
|
android = sh.Command(join(sdk_dir, 'tools', 'android'))
|
||||||
targets = android('list').stdout.decode('utf-8').split('\n')
|
targets = android('list').stdout.decode('utf-8').split('\n')
|
||||||
else:
|
else:
|
||||||
error('Could not find `android` or `sdkmanager` binaries in '
|
raise BuildInterruptingException(
|
||||||
'Android SDK. Exiting.')
|
'Could not find `android` or `sdkmanager` binaries in Android SDK',
|
||||||
exit(1)
|
instructions='Make sure the path to the Android SDK is correct')
|
||||||
apis = [s for s in targets if re.match(r'^ *API level: ', s)]
|
apis = [s for s in targets if re.match(r'^ *API level: ', s)]
|
||||||
apis = [re.findall(r'[0-9]+', s) for s in apis]
|
apis = [re.findall(r'[0-9]+', s) for s in apis]
|
||||||
apis = [int(s[0]) for s in apis if s]
|
apis = [int(s[0]) for s in apis if s]
|
||||||
|
@ -270,30 +251,28 @@ class Context(object):
|
||||||
info(('Requested API target {} is available, '
|
info(('Requested API target {} is available, '
|
||||||
'continuing.').format(android_api))
|
'continuing.').format(android_api))
|
||||||
else:
|
else:
|
||||||
warning(('Requested API target {} is not available, install '
|
raise BuildInterruptingException(
|
||||||
|
('Requested API target {} is not available, install '
|
||||||
'it with the SDK android tool.').format(android_api))
|
'it with the SDK android tool.').format(android_api))
|
||||||
warning('Exiting.')
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
# Find the Android NDK
|
# Find the Android NDK
|
||||||
# Could also use ANDROID_NDK, but doesn't look like many tools use this
|
# Could also use ANDROID_NDK, but doesn't look like many tools use this
|
||||||
ndk_dir = None
|
ndk_dir = None
|
||||||
if user_ndk_dir:
|
if user_ndk_dir:
|
||||||
ndk_dir = user_ndk_dir
|
ndk_dir = user_ndk_dir
|
||||||
if ndk_dir is not None:
|
|
||||||
info('Getting NDK dir from from user argument')
|
info('Getting NDK dir from from user argument')
|
||||||
if ndk_dir is None: # The old P4A-specific dir
|
if ndk_dir is None: # The old P4A-specific dir
|
||||||
ndk_dir = environ.get('ANDROIDNDK', None)
|
ndk_dir = environ.get('ANDROIDNDK', None)
|
||||||
if ndk_dir is not None:
|
if ndk_dir is not None:
|
||||||
info('Found NDK dir in $ANDROIDNDK')
|
info('Found NDK dir in $ANDROIDNDK: {}'.format(ndk_dir))
|
||||||
if ndk_dir is None: # Apparently the most common convention
|
if ndk_dir is None: # Apparently the most common convention
|
||||||
ndk_dir = environ.get('NDK_HOME', None)
|
ndk_dir = environ.get('NDK_HOME', None)
|
||||||
if ndk_dir is not None:
|
if ndk_dir is not None:
|
||||||
info('Found NDK dir in $NDK_HOME')
|
info('Found NDK dir in $NDK_HOME: {}'.format(ndk_dir))
|
||||||
if ndk_dir is None: # Another convention (with maven?)
|
if ndk_dir is None: # Another convention (with maven?)
|
||||||
ndk_dir = environ.get('ANDROID_NDK_HOME', None)
|
ndk_dir = environ.get('ANDROID_NDK_HOME', None)
|
||||||
if ndk_dir is not None:
|
if ndk_dir is not None:
|
||||||
info('Found NDK dir in $ANDROID_NDK_HOME')
|
info('Found NDK dir in $ANDROID_NDK_HOME: {}'.format(ndk_dir))
|
||||||
if ndk_dir is None: # Checks in the buildozer NDK dir, useful
|
if ndk_dir is None: # Checks in the buildozer NDK dir, useful
|
||||||
# # for debug tests of p4a
|
# # for debug tests of p4a
|
||||||
possible_dirs = glob.glob(expanduser(join(
|
possible_dirs = glob.glob(expanduser(join(
|
||||||
|
@ -307,62 +286,31 @@ class Context(object):
|
||||||
'maintain your own NDK download.')
|
'maintain your own NDK download.')
|
||||||
ndk_dir = possible_dirs[0]
|
ndk_dir = possible_dirs[0]
|
||||||
if ndk_dir is None:
|
if ndk_dir is None:
|
||||||
warning('Android NDK dir was not specified, exiting.')
|
raise BuildInterruptingException('Android NDK dir was not specified')
|
||||||
exit(1)
|
|
||||||
self.ndk_dir = realpath(ndk_dir)
|
self.ndk_dir = realpath(ndk_dir)
|
||||||
|
|
||||||
# Find the NDK version, and check it against what the NDK dir
|
check_ndk_version(ndk_dir)
|
||||||
# seems to report
|
|
||||||
ndk_ver = None
|
|
||||||
if user_ndk_ver:
|
|
||||||
ndk_ver = user_ndk_ver
|
|
||||||
if ndk_dir is not None:
|
|
||||||
info('Got NDK version from from user argument')
|
|
||||||
if ndk_ver is None:
|
|
||||||
ndk_ver = environ.get('ANDROIDNDKVER', None)
|
|
||||||
if ndk_dir is not None:
|
|
||||||
info('Got NDK version from $ANDROIDNDKVER')
|
|
||||||
|
|
||||||
self.ndk = 'google'
|
self.ndk = 'crystax' # force crystax detection
|
||||||
|
|
||||||
try:
|
ndk_api = None
|
||||||
with open(join(ndk_dir, 'RELEASE.TXT')) as fileh:
|
if user_ndk_api:
|
||||||
reported_ndk_ver = fileh.read().split(' ')[0].strip()
|
ndk_api = user_ndk_api
|
||||||
except IOError:
|
info('Getting NDK API version (i.e. minimum supported API) from user argument')
|
||||||
pass
|
elif 'NDKAPI' in environ:
|
||||||
|
ndk_api = environ.get('NDKAPI', None)
|
||||||
|
info('Found Android API target in $NDKAPI')
|
||||||
else:
|
else:
|
||||||
if reported_ndk_ver.startswith('crystax-ndk-'):
|
ndk_api = min(self.android_api, RECOMMENDED_NDK_API)
|
||||||
reported_ndk_ver = reported_ndk_ver[12:]
|
warning('NDK API target was not set manually, using '
|
||||||
self.ndk = 'crystax'
|
'the default of {} = min(android-api={}, default ndk-api={})'.format(
|
||||||
if ndk_ver is None:
|
ndk_api, self.android_api, RECOMMENDED_NDK_API))
|
||||||
ndk_ver = reported_ndk_ver
|
ndk_api = int(ndk_api)
|
||||||
info(('Got Android NDK version from the NDK dir: '
|
self.ndk_api = ndk_api
|
||||||
'it is {}').format(ndk_ver))
|
|
||||||
else:
|
|
||||||
if ndk_ver != reported_ndk_ver:
|
|
||||||
warning('NDK version was set as {}, but checking '
|
|
||||||
'the NDK dir claims it is {}.'.format(
|
|
||||||
ndk_ver, reported_ndk_ver))
|
|
||||||
warning('The build will try to continue, but it may '
|
|
||||||
'fail and you should check '
|
|
||||||
'that your setting is correct.')
|
|
||||||
warning('If the NDK dir result is correct, you don\'t '
|
|
||||||
'need to manually set the NDK ver.')
|
|
||||||
if ndk_ver is None:
|
|
||||||
warning('Android NDK version could not be found. This probably'
|
|
||||||
'won\'t cause any problems, but if necessary you can'
|
|
||||||
'set it with `--ndk-version=...`.')
|
|
||||||
self.ndk_ver = ndk_ver
|
|
||||||
|
|
||||||
info('Using {} NDK {}'.format(self.ndk.capitalize(), self.ndk_ver))
|
check_ndk_api(ndk_api, self.android_api)
|
||||||
|
|
||||||
virtualenv = None
|
virtualenv = get_virtualenv_executable()
|
||||||
if virtualenv is None:
|
|
||||||
virtualenv = sh.which('virtualenv2')
|
|
||||||
if virtualenv is None:
|
|
||||||
virtualenv = sh.which('virtualenv-2.7')
|
|
||||||
if virtualenv is None:
|
|
||||||
virtualenv = sh.which('virtualenv')
|
|
||||||
if virtualenv is None:
|
if virtualenv is None:
|
||||||
raise IOError('Couldn\'t find a virtualenv executable, '
|
raise IOError('Couldn\'t find a virtualenv executable, '
|
||||||
'you must install this to use p4a.')
|
'you must install this to use p4a.')
|
||||||
|
@ -374,14 +322,13 @@ class Context(object):
|
||||||
if not self.ccache:
|
if not self.ccache:
|
||||||
info('ccache is missing, the build will not be optimized in the '
|
info('ccache is missing, the build will not be optimized in the '
|
||||||
'future.')
|
'future.')
|
||||||
for cython_fn in ("cython2", "cython-2.7", "cython"):
|
for cython_fn in ("cython", "cython3", "cython2", "cython-2.7"):
|
||||||
cython = sh.which(cython_fn)
|
cython = sh.which(cython_fn)
|
||||||
if cython:
|
if cython:
|
||||||
self.cython = cython
|
self.cython = cython
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
error('No cython binary found. Exiting.')
|
raise BuildInterruptingException('No cython binary found.')
|
||||||
exit(1)
|
|
||||||
if not self.cython:
|
if not self.cython:
|
||||||
ok = False
|
ok = False
|
||||||
warning("Missing requirement: cython is not installed")
|
warning("Missing requirement: cython is not installed")
|
||||||
|
@ -394,9 +341,8 @@ class Context(object):
|
||||||
self.ndk_platform = join(
|
self.ndk_platform = join(
|
||||||
self.ndk_dir,
|
self.ndk_dir,
|
||||||
'platforms',
|
'platforms',
|
||||||
'android-{}'.format(self.android_min_api),
|
'android-{}'.format(self.ndk_api),
|
||||||
platform_dir)
|
platform_dir)
|
||||||
|
|
||||||
if not exists(self.ndk_platform):
|
if not exists(self.ndk_platform):
|
||||||
warning('ndk_platform doesn\'t exist: {}'.format(
|
warning('ndk_platform doesn\'t exist: {}'.format(
|
||||||
self.ndk_platform))
|
self.ndk_platform))
|
||||||
|
@ -408,7 +354,7 @@ class Context(object):
|
||||||
|
|
||||||
toolchain_versions = []
|
toolchain_versions = []
|
||||||
toolchain_path = join(self.ndk_dir, 'toolchains')
|
toolchain_path = join(self.ndk_dir, 'toolchains')
|
||||||
if os.path.isdir(toolchain_path):
|
if isdir(toolchain_path):
|
||||||
toolchain_contents = glob.glob('{}/{}-*'.format(toolchain_path,
|
toolchain_contents = glob.glob('{}/{}-*'.format(toolchain_path,
|
||||||
toolchain_prefix))
|
toolchain_prefix))
|
||||||
toolchain_versions = [split(path)[-1][len(toolchain_prefix) + 1:]
|
toolchain_versions = [split(path)[-1][len(toolchain_prefix) + 1:]
|
||||||
|
@ -456,9 +402,8 @@ class Context(object):
|
||||||
executable))
|
executable))
|
||||||
|
|
||||||
if not ok:
|
if not ok:
|
||||||
error('{}python-for-android cannot continue; aborting{}'.format(
|
raise BuildInterruptingException(
|
||||||
Err_Fore.RED, Err_Fore.RESET))
|
'python-for-android cannot continue due to the missing executables above')
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(Context, self).__init__()
|
super(Context, self).__init__()
|
||||||
|
@ -469,7 +414,7 @@ class Context(object):
|
||||||
self._sdk_dir = None
|
self._sdk_dir = None
|
||||||
self._ndk_dir = None
|
self._ndk_dir = None
|
||||||
self._android_api = None
|
self._android_api = None
|
||||||
self._ndk_ver = None
|
self._ndk_api = None
|
||||||
self.ndk = None
|
self.ndk = None
|
||||||
|
|
||||||
self.toolchain_prefix = None
|
self.toolchain_prefix = None
|
||||||
|
@ -483,6 +428,7 @@ class Context(object):
|
||||||
ArchARM(self),
|
ArchARM(self),
|
||||||
ArchARMv7_a(self),
|
ArchARMv7_a(self),
|
||||||
Archx86(self),
|
Archx86(self),
|
||||||
|
Archx86_64(self),
|
||||||
ArchAarch_64(self),
|
ArchAarch_64(self),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -504,8 +450,7 @@ class Context(object):
|
||||||
new_archs.add(match)
|
new_archs.add(match)
|
||||||
self.archs = list(new_archs)
|
self.archs = list(new_archs)
|
||||||
if not self.archs:
|
if not self.archs:
|
||||||
warning('Asked to compile for no Archs, so failing.')
|
raise BuildInterruptingException('Asked to compile for no Archs, so failing.')
|
||||||
exit(1)
|
|
||||||
info('Will compile for the following archs: {}'.format(
|
info('Will compile for the following archs: {}'.format(
|
||||||
', '.join([arch.arch for arch in self.archs])))
|
', '.join([arch.arch for arch in self.archs])))
|
||||||
|
|
||||||
|
@ -523,14 +468,10 @@ class Context(object):
|
||||||
'''Returns the location of site-packages in the python-install build
|
'''Returns the location of site-packages in the python-install build
|
||||||
dir.
|
dir.
|
||||||
'''
|
'''
|
||||||
|
if self.python_recipe.name == 'python2legacy':
|
||||||
# This needs to be replaced with something more general in
|
|
||||||
# order to support multiple python versions and/or multiple
|
|
||||||
# archs.
|
|
||||||
if self.python_recipe.from_crystax:
|
|
||||||
return self.get_python_install_dir()
|
|
||||||
return join(self.get_python_install_dir(),
|
return join(self.get_python_install_dir(),
|
||||||
'lib', 'python3.7', 'site-packages')
|
'lib', 'python2.7', 'site-packages')
|
||||||
|
return self.get_python_install_dir()
|
||||||
|
|
||||||
def get_libs_dir(self, arch):
|
def get_libs_dir(self, arch):
|
||||||
'''The libs dir for a given arch.'''
|
'''The libs dir for a given arch.'''
|
||||||
|
@ -541,9 +482,33 @@ class Context(object):
|
||||||
return exists(join(self.get_libs_dir(arch), lib))
|
return exists(join(self.get_libs_dir(arch), lib))
|
||||||
|
|
||||||
def has_package(self, name, arch=None):
|
def has_package(self, name, arch=None):
|
||||||
|
# If this is a file path, it'll need special handling:
|
||||||
|
if (name.find("/") >= 0 or name.find("\\") >= 0) and \
|
||||||
|
name.find("://") < 0: # (:// would indicate an url)
|
||||||
|
if not os.path.exists(name):
|
||||||
|
# Non-existing dir, cannot look this up.
|
||||||
|
return False
|
||||||
|
if os.path.exists(os.path.join(name, "setup.py")):
|
||||||
|
# Get name from setup.py:
|
||||||
|
name = subprocess.check_output([
|
||||||
|
sys.executable, "setup.py", "--name"],
|
||||||
|
cwd=name)
|
||||||
|
try:
|
||||||
|
name = name.decode('utf-8', 'replace')
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
name = name.strip()
|
||||||
|
if len(name) == 0:
|
||||||
|
# Failed to look up any meaningful name.
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# A folder with whatever, cannot look this up.
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Try to look up recipe by name:
|
||||||
try:
|
try:
|
||||||
recipe = Recipe.get_recipe(name, self)
|
recipe = Recipe.get_recipe(name, self)
|
||||||
except IOError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
name = getattr(recipe, 'site_packages_name', None) or name
|
name = getattr(recipe, 'site_packages_name', None) or name
|
||||||
|
@ -562,7 +527,6 @@ class Context(object):
|
||||||
|
|
||||||
def build_recipes(build_order, python_modules, ctx):
|
def build_recipes(build_order, python_modules, ctx):
|
||||||
# Put recipes in correct build order
|
# Put recipes in correct build order
|
||||||
bs = ctx.bootstrap
|
|
||||||
info_notify("Recipe build order is {}".format(build_order))
|
info_notify("Recipe build order is {}".format(build_order))
|
||||||
if python_modules:
|
if python_modules:
|
||||||
python_modules = sorted(set(python_modules))
|
python_modules = sorted(set(python_modules))
|
||||||
|
@ -635,7 +599,13 @@ def run_pymodules_install(ctx, modules):
|
||||||
|
|
||||||
venv = sh.Command(ctx.virtualenv)
|
venv = sh.Command(ctx.virtualenv)
|
||||||
with current_directory(join(ctx.build_dir)):
|
with current_directory(join(ctx.build_dir)):
|
||||||
shprint(venv, '--python=python3.7', 'venv')
|
shprint(venv,
|
||||||
|
'--python=python{}'.format(
|
||||||
|
ctx.python_recipe.major_minor_version_string.
|
||||||
|
partition(".")[0]
|
||||||
|
),
|
||||||
|
'venv'
|
||||||
|
)
|
||||||
|
|
||||||
info('Creating a requirements.txt file for the Python modules')
|
info('Creating a requirements.txt file for the Python modules')
|
||||||
with open('requirements.txt', 'w') as fileh:
|
with open('requirements.txt', 'w') as fileh:
|
||||||
|
@ -647,18 +617,63 @@ def run_pymodules_install(ctx, modules):
|
||||||
line = '{}\n'.format(module)
|
line = '{}\n'.format(module)
|
||||||
fileh.write(line)
|
fileh.write(line)
|
||||||
|
|
||||||
info('Installing Python modules with pip')
|
# Prepare base environment and upgrade pip:
|
||||||
info('If this fails with a message about /bin/false, this '
|
base_env = copy.copy(os.environ)
|
||||||
'probably means the package cannot be installed with '
|
base_env["PYTHONPATH"] = ctx.get_site_packages_dir()
|
||||||
'pip as it needs a compilation recipe.')
|
info('Upgrade pip to latest version')
|
||||||
|
shprint(sh.bash, '-c', (
|
||||||
|
"source venv/bin/activate && pip install -U pip"
|
||||||
|
), _env=copy.copy(base_env))
|
||||||
|
|
||||||
# This bash method is what old-p4a used
|
# Install Cython in case modules need it to build:
|
||||||
# It works but should be replaced with something better
|
info('Install Cython in case one of the modules needs it to build')
|
||||||
|
shprint(sh.bash, '-c', (
|
||||||
|
"venv/bin/pip install Cython"
|
||||||
|
), _env=copy.copy(base_env))
|
||||||
|
|
||||||
|
# Get environment variables for build (with CC/compiler set):
|
||||||
|
standard_recipe = CythonRecipe()
|
||||||
|
standard_recipe.ctx = ctx
|
||||||
|
# (note: following line enables explicit -lpython... linker options)
|
||||||
|
standard_recipe.call_hostpython_via_targetpython = False
|
||||||
|
recipe_env = standard_recipe.get_recipe_env(ctx.archs[0])
|
||||||
|
env = copy.copy(base_env)
|
||||||
|
env.update(recipe_env)
|
||||||
|
|
||||||
|
info('Installing Python modules with pip')
|
||||||
|
info('IF THIS FAILS, THE MODULES MAY NEED A RECIPE. '
|
||||||
|
'A reason for this is often modules compiling '
|
||||||
|
'native code that is unaware of Android cross-compilation '
|
||||||
|
'and does not work without additional '
|
||||||
|
'changes / workarounds.')
|
||||||
|
|
||||||
|
# Make sure our build package dir is available, and the virtualenv
|
||||||
|
# site packages come FIRST (so the proper pip version is used):
|
||||||
|
env["PYTHONPATH"] += ":" + ctx.get_site_packages_dir()
|
||||||
|
env["PYTHONPATH"] = os.path.abspath(join(
|
||||||
|
ctx.build_dir, "venv", "lib",
|
||||||
|
"python" + ctx.python_recipe.major_minor_version_string,
|
||||||
|
"site-packages")) + ":" + env["PYTHONPATH"]
|
||||||
|
|
||||||
|
'''
|
||||||
|
# Do actual install:
|
||||||
|
shprint(sh.bash, '-c', (
|
||||||
|
"venv/bin/pip " +
|
||||||
|
"install -v --target '{0}' --no-deps -r requirements.txt"
|
||||||
|
).format(ctx.get_site_packages_dir().replace("'", "'\"'\"'")),
|
||||||
|
_env=copy.copy(env))
|
||||||
|
'''
|
||||||
|
|
||||||
|
# use old install script
|
||||||
shprint(sh.bash, '-c', (
|
shprint(sh.bash, '-c', (
|
||||||
"source venv/bin/activate && env CC=/bin/false CXX=/bin/false "
|
"source venv/bin/activate && env CC=/bin/false CXX=/bin/false "
|
||||||
"PYTHONPATH={0} pip install --target '{0}' --no-deps -r requirements.txt"
|
"PYTHONPATH={0} pip install --target '{0}' --no-deps -r requirements.txt"
|
||||||
).format(ctx.get_site_packages_dir()))
|
).format(ctx.get_site_packages_dir()))
|
||||||
|
|
||||||
|
# Strip object files after potential Cython or native code builds:
|
||||||
|
standard_recipe.strip_object_files(ctx.archs[0], env,
|
||||||
|
build_dir=ctx.build_dir)
|
||||||
|
|
||||||
|
|
||||||
def biglink(ctx, arch):
|
def biglink(ctx, arch):
|
||||||
# First, collate object files from each recipe
|
# First, collate object files from each recipe
|
||||||
|
|
|
@ -2,9 +2,9 @@ from os.path import exists, join
|
||||||
import glob
|
import glob
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from pythonforandroid.logger import (info, info_notify, warning,
|
from pythonforandroid.logger import (info, info_notify, warning, Err_Style, Err_Fore)
|
||||||
Err_Style, Err_Fore)
|
from pythonforandroid.util import current_directory, BuildInterruptingException
|
||||||
from pythonforandroid.util import current_directory
|
from shutil import rmtree
|
||||||
|
|
||||||
|
|
||||||
class Distribution(object):
|
class Distribution(object):
|
||||||
|
@ -21,6 +21,7 @@ class Distribution(object):
|
||||||
needs_build = False # Whether the dist needs compiling
|
needs_build = False # Whether the dist needs compiling
|
||||||
url = None
|
url = None
|
||||||
dist_dir = None # Where the dist dir ultimately is. Should not be None.
|
dist_dir = None # Where the dist dir ultimately is. Should not be None.
|
||||||
|
ndk_api = None
|
||||||
|
|
||||||
archs = []
|
archs = []
|
||||||
'''The arch targets that the dist is built for.'''
|
'''The arch targets that the dist is built for.'''
|
||||||
|
@ -42,9 +43,11 @@ class Distribution(object):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_distribution(cls, ctx, name=None, recipes=[],
|
def get_distribution(cls, ctx, name=None, recipes=[],
|
||||||
|
ndk_api=None,
|
||||||
force_build=False,
|
force_build=False,
|
||||||
extra_dist_dirs=[],
|
extra_dist_dirs=[],
|
||||||
require_perfect_match=False):
|
require_perfect_match=False,
|
||||||
|
allow_replace_dist=True):
|
||||||
'''Takes information about the distribution, and decides what kind of
|
'''Takes information about the distribution, and decides what kind of
|
||||||
distribution it will be.
|
distribution it will be.
|
||||||
|
|
||||||
|
@ -68,21 +71,31 @@ class Distribution(object):
|
||||||
require_perfect_match : bool
|
require_perfect_match : bool
|
||||||
If True, will only match distributions with precisely the
|
If True, will only match distributions with precisely the
|
||||||
correct set of recipes.
|
correct set of recipes.
|
||||||
|
allow_replace_dist : bool
|
||||||
|
If True, will allow an existing dist with the specified
|
||||||
|
name but incompatible requirements to be overwritten by
|
||||||
|
a new one with the current requirements.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
existing_dists = Distribution.get_distributions(ctx)
|
existing_dists = Distribution.get_distributions(ctx)
|
||||||
|
|
||||||
needs_build = True # whether the dist needs building, will be returned
|
|
||||||
|
|
||||||
possible_dists = existing_dists
|
possible_dists = existing_dists
|
||||||
|
|
||||||
|
name_match_dist = None
|
||||||
|
|
||||||
# 0) Check if a dist with that name already exists
|
# 0) Check if a dist with that name already exists
|
||||||
if name is not None and name:
|
if name is not None and name:
|
||||||
possible_dists = [d for d in possible_dists if d.name == name]
|
possible_dists = [d for d in possible_dists if d.name == name]
|
||||||
|
if possible_dists:
|
||||||
|
name_match_dist = possible_dists[0]
|
||||||
|
|
||||||
# 1) Check if any existing dists meet the requirements
|
# 1) Check if any existing dists meet the requirements
|
||||||
_possible_dists = []
|
_possible_dists = []
|
||||||
for dist in possible_dists:
|
for dist in possible_dists:
|
||||||
|
if (
|
||||||
|
ndk_api is not None and dist.ndk_api != ndk_api
|
||||||
|
) or dist.ndk_api is None:
|
||||||
|
continue
|
||||||
for recipe in recipes:
|
for recipe in recipes:
|
||||||
if recipe not in dist.recipes:
|
if recipe not in dist.recipes:
|
||||||
break
|
break
|
||||||
|
@ -97,10 +110,12 @@ class Distribution(object):
|
||||||
else:
|
else:
|
||||||
info('No existing dists meet the given requirements!')
|
info('No existing dists meet the given requirements!')
|
||||||
|
|
||||||
# If any dist has perfect recipes, return it
|
# If any dist has perfect recipes and ndk API, return it
|
||||||
for dist in possible_dists:
|
for dist in possible_dists:
|
||||||
if force_build:
|
if force_build:
|
||||||
continue
|
continue
|
||||||
|
if ndk_api is not None and dist.ndk_api != ndk_api:
|
||||||
|
continue
|
||||||
if (set(dist.recipes) == set(recipes) or
|
if (set(dist.recipes) == set(recipes) or
|
||||||
(set(recipes).issubset(set(dist.recipes)) and
|
(set(recipes).issubset(set(dist.recipes)) and
|
||||||
not require_perfect_match)):
|
not require_perfect_match)):
|
||||||
|
@ -110,33 +125,20 @@ class Distribution(object):
|
||||||
|
|
||||||
assert len(possible_dists) < 2
|
assert len(possible_dists) < 2
|
||||||
|
|
||||||
if not name and possible_dists:
|
# If there was a name match but we didn't already choose it,
|
||||||
info('Asked for dist with name {} with recipes ({}), but a dist '
|
# then the existing dist is incompatible with the requested
|
||||||
'with this name already exists and has incompatible recipes '
|
# configuration and the build cannot continue
|
||||||
'({})'.format(name, ', '.join(recipes),
|
if name_match_dist is not None and not allow_replace_dist:
|
||||||
', '.join(possible_dists[0].recipes)))
|
raise BuildInterruptingException(
|
||||||
info('No compatible dist found, so exiting.')
|
'Asked for dist with name {name} with recipes ({req_recipes}) and '
|
||||||
exit(1)
|
'NDK API {req_ndk_api}, but a dist '
|
||||||
|
'with this name already exists and has either incompatible recipes '
|
||||||
# # 2) Check if any downloadable dists meet the requirements
|
'({dist_recipes}) or NDK API {dist_ndk_api}'.format(
|
||||||
|
name=name,
|
||||||
# online_dists = [('testsdl2', ['hostpython2', 'sdl2_image',
|
req_ndk_api=ndk_api,
|
||||||
# 'sdl2_mixer', 'sdl2_ttf',
|
dist_ndk_api=name_match_dist.ndk_api,
|
||||||
# 'python2', 'sdl2',
|
req_recipes=', '.join(recipes),
|
||||||
# 'pyjniussdl2', 'kivysdl2'],
|
dist_recipes=', '.join(name_match_dist.recipes)))
|
||||||
# 'https://github.com/inclement/sdl2-example-dist/archive/master.zip'),
|
|
||||||
# ]
|
|
||||||
# _possible_dists = []
|
|
||||||
# for dist_name, dist_recipes, dist_url in online_dists:
|
|
||||||
# for recipe in recipes:
|
|
||||||
# if recipe not in dist_recipes:
|
|
||||||
# break
|
|
||||||
# else:
|
|
||||||
# dist = Distribution(ctx)
|
|
||||||
# dist.name = dist_name
|
|
||||||
# dist.url = dist_url
|
|
||||||
# _possible_dists.append(dist)
|
|
||||||
# # if _possible_dists
|
|
||||||
|
|
||||||
# If we got this far, we need to build a new dist
|
# If we got this far, we need to build a new dist
|
||||||
dist = Distribution(ctx)
|
dist = Distribution(ctx)
|
||||||
|
@ -152,16 +154,23 @@ class Distribution(object):
|
||||||
dist.name = name
|
dist.name = name
|
||||||
dist.dist_dir = join(ctx.dist_dir, dist.name)
|
dist.dist_dir = join(ctx.dist_dir, dist.name)
|
||||||
dist.recipes = recipes
|
dist.recipes = recipes
|
||||||
|
dist.ndk_api = ctx.ndk_api
|
||||||
|
|
||||||
return dist
|
return dist
|
||||||
|
|
||||||
|
def folder_exists(self):
|
||||||
|
return exists(self.dist_dir)
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
rmtree(self.dist_dir)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_distributions(cls, ctx, extra_dist_dirs=[]):
|
def get_distributions(cls, ctx, extra_dist_dirs=[]):
|
||||||
'''Returns all the distributions found locally.'''
|
'''Returns all the distributions found locally.'''
|
||||||
if extra_dist_dirs:
|
if extra_dist_dirs:
|
||||||
warning('extra_dist_dirs argument to get_distributions '
|
raise BuildInterruptingException(
|
||||||
|
'extra_dist_dirs argument to get_distributions '
|
||||||
'is not yet implemented')
|
'is not yet implemented')
|
||||||
exit(1)
|
|
||||||
dist_dir = ctx.dist_dir
|
dist_dir = ctx.dist_dir
|
||||||
folders = glob.glob(join(dist_dir, '*'))
|
folders = glob.glob(join(dist_dir, '*'))
|
||||||
for dir in extra_dist_dirs:
|
for dir in extra_dist_dirs:
|
||||||
|
@ -179,40 +188,47 @@ class Distribution(object):
|
||||||
dist.recipes = dist_info['recipes']
|
dist.recipes = dist_info['recipes']
|
||||||
if 'archs' in dist_info:
|
if 'archs' in dist_info:
|
||||||
dist.archs = dist_info['archs']
|
dist.archs = dist_info['archs']
|
||||||
|
if 'ndk_api' in dist_info:
|
||||||
|
dist.ndk_api = dist_info['ndk_api']
|
||||||
|
else:
|
||||||
|
dist.ndk_api = None
|
||||||
|
warning(
|
||||||
|
"Distribution {distname}: ({distdir}) has been "
|
||||||
|
"built with an unknown api target, ignoring it, "
|
||||||
|
"you might want to delete it".format(
|
||||||
|
distname=dist.name,
|
||||||
|
distdir=dist.dist_dir
|
||||||
|
)
|
||||||
|
)
|
||||||
dists.append(dist)
|
dists.append(dist)
|
||||||
return dists
|
return dists
|
||||||
|
|
||||||
def save_info(self):
|
def save_info(self, dirn):
|
||||||
'''
|
'''
|
||||||
Save information about the distribution in its dist_dir.
|
Save information about the distribution in its dist_dir.
|
||||||
'''
|
'''
|
||||||
with current_directory(self.dist_dir):
|
with current_directory(dirn):
|
||||||
info('Saving distribution info')
|
info('Saving distribution info')
|
||||||
with open('dist_info.json', 'w') as fileh:
|
with open('dist_info.json', 'w') as fileh:
|
||||||
json.dump({'dist_name': self.name,
|
json.dump({'dist_name': self.ctx.dist_name,
|
||||||
|
'bootstrap': self.ctx.bootstrap.name,
|
||||||
'archs': [arch.arch for arch in self.ctx.archs],
|
'archs': [arch.arch for arch in self.ctx.archs],
|
||||||
'recipes': self.ctx.recipe_build_order},
|
'ndk_api': self.ctx.ndk_api,
|
||||||
|
'recipes': self.ctx.recipe_build_order + self.ctx.python_modules,
|
||||||
|
'hostpython': self.ctx.hostpython,
|
||||||
|
'python_version': self.ctx.python_recipe.major_minor_version_string},
|
||||||
fileh)
|
fileh)
|
||||||
|
|
||||||
def load_info(self):
|
|
||||||
'''Load information about the dist from the info file that p4a
|
|
||||||
automatically creates.'''
|
|
||||||
with current_directory(self.dist_dir):
|
|
||||||
filen = 'dist_info.json'
|
|
||||||
if not exists(filen):
|
|
||||||
return None
|
|
||||||
with open('dist_info.json', 'r') as fileh:
|
|
||||||
dist_info = json.load(fileh)
|
|
||||||
return dist_info
|
|
||||||
|
|
||||||
|
|
||||||
def pretty_log_dists(dists, log_func=info):
|
def pretty_log_dists(dists, log_func=info):
|
||||||
infos = []
|
infos = []
|
||||||
for dist in dists:
|
for dist in dists:
|
||||||
infos.append('{Fore.GREEN}{Style.BRIGHT}{name}{Style.RESET_ALL}: '
|
ndk_api = 'unknown' if dist.ndk_api is None else dist.ndk_api
|
||||||
|
infos.append('{Fore.GREEN}{Style.BRIGHT}{name}{Style.RESET_ALL}: min API {ndk_api}, '
|
||||||
'includes recipes ({Fore.GREEN}{recipes}'
|
'includes recipes ({Fore.GREEN}{recipes}'
|
||||||
'{Style.RESET_ALL}), built for archs ({Fore.BLUE}'
|
'{Style.RESET_ALL}), built for archs ({Fore.BLUE}'
|
||||||
'{archs}{Style.RESET_ALL})'.format(
|
'{archs}{Style.RESET_ALL})'.format(
|
||||||
|
ndk_api=ndk_api,
|
||||||
name=dist.name, recipes=', '.join(dist.recipes),
|
name=dist.name, recipes=', '.join(dist.recipes),
|
||||||
archs=', '.join(dist.archs) if dist.archs else 'UNKNOWN',
|
archs=', '.join(dist.archs) if dist.archs else 'UNKNOWN',
|
||||||
Fore=Err_Fore, Style=Err_Style))
|
Fore=Err_Fore, Style=Err_Style))
|
||||||
|
|
|
@ -1,24 +1,37 @@
|
||||||
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from itertools import product
|
from itertools import product
|
||||||
from sys import exit
|
|
||||||
|
|
||||||
from pythonforandroid.logger import (info, warning, error)
|
from pythonforandroid.logger import info
|
||||||
from pythonforandroid.recipe import Recipe
|
from pythonforandroid.recipe import Recipe
|
||||||
from pythonforandroid.bootstrap import Bootstrap
|
from pythonforandroid.bootstrap import Bootstrap
|
||||||
|
from pythonforandroid.util import BuildInterruptingException
|
||||||
|
|
||||||
|
|
||||||
|
def fix_deplist(deps):
|
||||||
|
""" Turn a dependency list into lowercase, and make sure all entries
|
||||||
|
that are just a string become a tuple of strings
|
||||||
|
"""
|
||||||
|
deps = [
|
||||||
|
((dep.lower(),)
|
||||||
|
if not isinstance(dep, (list, tuple))
|
||||||
|
else tuple([dep_entry.lower()
|
||||||
|
for dep_entry in dep
|
||||||
|
]))
|
||||||
|
for dep in deps
|
||||||
|
]
|
||||||
|
return deps
|
||||||
|
|
||||||
|
|
||||||
class RecipeOrder(dict):
|
class RecipeOrder(dict):
|
||||||
|
|
||||||
def __init__(self, ctx):
|
def __init__(self, ctx):
|
||||||
self.ctx = ctx
|
self.ctx = ctx
|
||||||
|
|
||||||
def conflicts(self, name):
|
def conflicts(self):
|
||||||
for name in self.keys():
|
for name in self.keys():
|
||||||
try:
|
try:
|
||||||
recipe = Recipe.get_recipe(name, self.ctx)
|
recipe = Recipe.get_recipe(name, self.ctx)
|
||||||
conflicts = recipe.conflicts
|
conflicts = [dep.lower() for dep in recipe.conflicts]
|
||||||
except IOError:
|
except ValueError:
|
||||||
conflicts = []
|
conflicts = []
|
||||||
|
|
||||||
if any([c in self for c in conflicts]):
|
if any([c in self for c in conflicts]):
|
||||||
|
@ -26,26 +39,59 @@ class RecipeOrder(dict):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def recursively_collect_orders(name, ctx, orders=[]):
|
def get_dependency_tuple_list_for_recipe(recipe, blacklist=None):
|
||||||
|
""" Get the dependencies of a recipe with filtered out blacklist, and
|
||||||
|
turned into tuples with fix_deplist()
|
||||||
|
"""
|
||||||
|
if blacklist is None:
|
||||||
|
blacklist = set()
|
||||||
|
assert(type(blacklist) == set)
|
||||||
|
if recipe.depends is None:
|
||||||
|
dependencies = []
|
||||||
|
else:
|
||||||
|
# Turn all dependencies into tuples so that product will work
|
||||||
|
dependencies = fix_deplist(recipe.depends)
|
||||||
|
|
||||||
|
# Filter out blacklisted items and turn lowercase:
|
||||||
|
dependencies = [
|
||||||
|
tuple(set(deptuple) - blacklist)
|
||||||
|
for deptuple in dependencies
|
||||||
|
if tuple(set(deptuple) - blacklist)
|
||||||
|
]
|
||||||
|
return dependencies
|
||||||
|
|
||||||
|
|
||||||
|
def recursively_collect_orders(
|
||||||
|
name, ctx, all_inputs, orders=None, blacklist=None
|
||||||
|
):
|
||||||
'''For each possible recipe ordering, try to add the new recipe name
|
'''For each possible recipe ordering, try to add the new recipe name
|
||||||
to that order. Recursively do the same thing with all the
|
to that order. Recursively do the same thing with all the
|
||||||
dependencies of each recipe.
|
dependencies of each recipe.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
name = name.lower()
|
||||||
|
if orders is None:
|
||||||
|
orders = []
|
||||||
|
if blacklist is None:
|
||||||
|
blacklist = set()
|
||||||
try:
|
try:
|
||||||
recipe = Recipe.get_recipe(name, ctx)
|
recipe = Recipe.get_recipe(name, ctx)
|
||||||
if recipe.depends is None:
|
dependencies = get_dependency_tuple_list_for_recipe(
|
||||||
dependencies = []
|
recipe, blacklist=blacklist
|
||||||
else:
|
)
|
||||||
# make all dependencies into lists so that product will work
|
|
||||||
dependencies = [([dependency] if not isinstance(
|
# handle opt_depends: these impose requirements on the build
|
||||||
dependency, (list, tuple))
|
# order only if already present in the list of recipes to build
|
||||||
else dependency) for dependency in recipe.depends]
|
dependencies.extend(fix_deplist(
|
||||||
|
[[d] for d in recipe.get_opt_depends_in_list(all_inputs)
|
||||||
|
if d.lower() not in blacklist]
|
||||||
|
))
|
||||||
|
|
||||||
if recipe.conflicts is None:
|
if recipe.conflicts is None:
|
||||||
conflicts = []
|
conflicts = []
|
||||||
else:
|
else:
|
||||||
conflicts = recipe.conflicts
|
conflicts = [dep.lower() for dep in recipe.conflicts]
|
||||||
except IOError:
|
except ValueError:
|
||||||
# The recipe does not exist, so we assume it can be installed
|
# The recipe does not exist, so we assume it can be installed
|
||||||
# via pip with no extra dependencies
|
# via pip with no extra dependencies
|
||||||
dependencies = []
|
dependencies = []
|
||||||
|
@ -57,7 +103,7 @@ def recursively_collect_orders(name, ctx, orders=[]):
|
||||||
if name in order:
|
if name in order:
|
||||||
new_orders.append(deepcopy(order))
|
new_orders.append(deepcopy(order))
|
||||||
continue
|
continue
|
||||||
if order.conflicts(name):
|
if order.conflicts():
|
||||||
continue
|
continue
|
||||||
if any([conflict in order for conflict in conflicts]):
|
if any([conflict in order for conflict in conflicts]):
|
||||||
continue
|
continue
|
||||||
|
@ -69,7 +115,9 @@ def recursively_collect_orders(name, ctx, orders=[]):
|
||||||
dependency_new_orders = [new_order]
|
dependency_new_orders = [new_order]
|
||||||
for dependency in dependency_set:
|
for dependency in dependency_set:
|
||||||
dependency_new_orders = recursively_collect_orders(
|
dependency_new_orders = recursively_collect_orders(
|
||||||
dependency, ctx, dependency_new_orders)
|
dependency, ctx, all_inputs, dependency_new_orders,
|
||||||
|
blacklist=blacklist
|
||||||
|
)
|
||||||
|
|
||||||
new_orders.extend(dependency_new_orders)
|
new_orders.extend(dependency_new_orders)
|
||||||
|
|
||||||
|
@ -95,22 +143,142 @@ def find_order(graph):
|
||||||
bset.discard(result)
|
bset.discard(result)
|
||||||
|
|
||||||
|
|
||||||
def get_recipe_order_and_bootstrap(ctx, names, bs=None):
|
def obvious_conflict_checker(ctx, name_tuples, blacklist=None):
|
||||||
recipes_to_load = set(names)
|
""" This is a pre-flight check function that will completely ignore
|
||||||
if bs is not None and bs.recipe_depends:
|
recipe order or choosing an actual value in any of the multiple
|
||||||
recipes_to_load = recipes_to_load.union(set(bs.recipe_depends))
|
choice tuples/dependencies, and just do a very basic obvious
|
||||||
|
conflict check.
|
||||||
|
"""
|
||||||
|
deps_were_added_by = dict()
|
||||||
|
deps = set()
|
||||||
|
if blacklist is None:
|
||||||
|
blacklist = set()
|
||||||
|
|
||||||
possible_orders = []
|
# Add dependencies for all recipes:
|
||||||
|
to_be_added = [(name_tuple, None) for name_tuple in name_tuples]
|
||||||
|
while len(to_be_added) > 0:
|
||||||
|
current_to_be_added = list(to_be_added)
|
||||||
|
to_be_added = []
|
||||||
|
for (added_tuple, adding_recipe) in current_to_be_added:
|
||||||
|
assert(type(added_tuple) == tuple)
|
||||||
|
if len(added_tuple) > 1:
|
||||||
|
# No obvious commitment in what to add, don't check it itself
|
||||||
|
# but throw it into deps for later comparing against
|
||||||
|
# (Remember this function only catches obvious issues)
|
||||||
|
deps.add(added_tuple)
|
||||||
|
continue
|
||||||
|
|
||||||
|
name = added_tuple[0]
|
||||||
|
recipe_conflicts = set()
|
||||||
|
recipe_dependencies = []
|
||||||
|
try:
|
||||||
|
# Get recipe to add and who's ultimately adding it:
|
||||||
|
recipe = Recipe.get_recipe(name, ctx)
|
||||||
|
recipe_conflicts = {c.lower() for c in recipe.conflicts}
|
||||||
|
recipe_dependencies = get_dependency_tuple_list_for_recipe(
|
||||||
|
recipe, blacklist=blacklist
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
adder_first_recipe_name = adding_recipe or name
|
||||||
|
|
||||||
|
# Collect the conflicts:
|
||||||
|
triggered_conflicts = []
|
||||||
|
for dep_tuple_list in deps:
|
||||||
|
# See if the new deps conflict with things added before:
|
||||||
|
if set(dep_tuple_list).intersection(
|
||||||
|
recipe_conflicts) == set(dep_tuple_list):
|
||||||
|
triggered_conflicts.append(dep_tuple_list)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# See if what was added before conflicts with the new deps:
|
||||||
|
if len(dep_tuple_list) > 1:
|
||||||
|
# Not an obvious commitment to a specific recipe/dep
|
||||||
|
# to be added, so we won't check.
|
||||||
|
# (remember this function only catches obvious issues)
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
dep_recipe = Recipe.get_recipe(dep_tuple_list[0], ctx)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
conflicts = [c.lower() for c in dep_recipe.conflicts]
|
||||||
|
if name in conflicts:
|
||||||
|
triggered_conflicts.append(dep_tuple_list)
|
||||||
|
|
||||||
|
# Throw error on conflict:
|
||||||
|
if triggered_conflicts:
|
||||||
|
# Get first conflict and see who added that one:
|
||||||
|
adder_second_recipe_name = "'||'".join(triggered_conflicts[0])
|
||||||
|
second_recipe_original_adder = deps_were_added_by.get(
|
||||||
|
(adder_second_recipe_name,), None
|
||||||
|
)
|
||||||
|
if second_recipe_original_adder:
|
||||||
|
adder_second_recipe_name = second_recipe_original_adder
|
||||||
|
|
||||||
|
# Prompt error:
|
||||||
|
raise BuildInterruptingException(
|
||||||
|
"Conflict detected: '{}'"
|
||||||
|
" inducing dependencies {}, and '{}'"
|
||||||
|
" inducing conflicting dependencies {}".format(
|
||||||
|
adder_first_recipe_name,
|
||||||
|
(recipe.name,),
|
||||||
|
adder_second_recipe_name,
|
||||||
|
triggered_conflicts[0]
|
||||||
|
))
|
||||||
|
|
||||||
|
# Actually add it to our list:
|
||||||
|
deps.add(added_tuple)
|
||||||
|
deps_were_added_by[added_tuple] = adding_recipe
|
||||||
|
|
||||||
|
# Schedule dependencies to be added
|
||||||
|
to_be_added += [
|
||||||
|
(dep, adder_first_recipe_name or name)
|
||||||
|
for dep in recipe_dependencies
|
||||||
|
if dep not in deps
|
||||||
|
]
|
||||||
|
# If we came here, then there were no obvious conflicts.
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_recipe_order_and_bootstrap(ctx, names, bs=None, blacklist=None):
|
||||||
|
# Get set of recipe/dependency names, clean up and add bootstrap deps:
|
||||||
|
names = set(names)
|
||||||
|
if bs is not None and bs.recipe_depends:
|
||||||
|
names = names.union(set(bs.recipe_depends))
|
||||||
|
names = fix_deplist([
|
||||||
|
([name] if not isinstance(name, (list, tuple)) else name)
|
||||||
|
for name in names
|
||||||
|
])
|
||||||
|
if blacklist is None:
|
||||||
|
blacklist = set()
|
||||||
|
blacklist = {bitem.lower() for bitem in blacklist}
|
||||||
|
|
||||||
|
# Remove all values that are in the blacklist:
|
||||||
|
names_before_blacklist = list(names)
|
||||||
|
names = []
|
||||||
|
for name in names_before_blacklist:
|
||||||
|
cleaned_up_tuple = tuple([
|
||||||
|
item for item in name if item not in blacklist
|
||||||
|
])
|
||||||
|
if cleaned_up_tuple:
|
||||||
|
names.append(cleaned_up_tuple)
|
||||||
|
|
||||||
|
# Do check for obvious conflicts (that would trigger in any order, and
|
||||||
|
# without comitting to any specific choice in a multi-choice tuple of
|
||||||
|
# dependencies):
|
||||||
|
obvious_conflict_checker(ctx, names, blacklist=blacklist)
|
||||||
|
# If we get here, no obvious conflicts!
|
||||||
|
|
||||||
# get all possible order graphs, as names may include tuples/lists
|
# get all possible order graphs, as names may include tuples/lists
|
||||||
# of alternative dependencies
|
# of alternative dependencies
|
||||||
names = [([name] if not isinstance(name, (list, tuple)) else name)
|
possible_orders = []
|
||||||
for name in names]
|
|
||||||
for name_set in product(*names):
|
for name_set in product(*names):
|
||||||
new_possible_orders = [RecipeOrder(ctx)]
|
new_possible_orders = [RecipeOrder(ctx)]
|
||||||
for name in name_set:
|
for name in name_set:
|
||||||
new_possible_orders = recursively_collect_orders(
|
new_possible_orders = recursively_collect_orders(
|
||||||
name, ctx, orders=new_possible_orders)
|
name, ctx, name_set, orders=new_possible_orders,
|
||||||
|
blacklist=blacklist
|
||||||
|
)
|
||||||
possible_orders.extend(new_possible_orders)
|
possible_orders.extend(new_possible_orders)
|
||||||
|
|
||||||
# turn each order graph into a linear list if possible
|
# turn each order graph into a linear list if possible
|
||||||
|
@ -122,23 +290,18 @@ def get_recipe_order_and_bootstrap(ctx, names, bs=None):
|
||||||
info('Circular dependency found in graph {}, skipping it.'.format(
|
info('Circular dependency found in graph {}, skipping it.'.format(
|
||||||
possible_order))
|
possible_order))
|
||||||
continue
|
continue
|
||||||
except:
|
|
||||||
warning('Failed to import recipe named {}; the recipe exists '
|
|
||||||
'but appears broken.'.format(name))
|
|
||||||
warning('Exception was:')
|
|
||||||
raise
|
|
||||||
orders.append(list(order))
|
orders.append(list(order))
|
||||||
|
|
||||||
# prefer python2 and SDL2 if available
|
# prefer python3 and SDL2 if available
|
||||||
orders = sorted(orders,
|
orders = sorted(orders,
|
||||||
key=lambda order: -('python2' in order) - ('sdl2' in order))
|
key=lambda order: -('python3' in order) - ('sdl2' in order))
|
||||||
|
|
||||||
if not orders:
|
if not orders:
|
||||||
error('Didn\'t find any valid dependency graphs.')
|
raise BuildInterruptingException(
|
||||||
error('This means that some of your requirements pull in '
|
'Didn\'t find any valid dependency graphs. '
|
||||||
'conflicting dependencies.')
|
'This means that some of your '
|
||||||
error('Exiting.')
|
'requirements pull in conflicting dependencies.')
|
||||||
exit(1)
|
|
||||||
# It would be better to check against possible orders other
|
# It would be better to check against possible orders other
|
||||||
# than the first one, but in practice clashes will be rare,
|
# than the first one, but in practice clashes will be rare,
|
||||||
# and can be resolved by specifying more parameters
|
# and can be resolved by specifying more parameters
|
||||||
|
@ -153,18 +316,26 @@ def get_recipe_order_and_bootstrap(ctx, names, bs=None):
|
||||||
|
|
||||||
if bs is None:
|
if bs is None:
|
||||||
bs = Bootstrap.get_bootstrap_from_recipes(chosen_order, ctx)
|
bs = Bootstrap.get_bootstrap_from_recipes(chosen_order, ctx)
|
||||||
|
if bs is None:
|
||||||
|
# Note: don't remove this without thought, causes infinite loop
|
||||||
|
raise BuildInterruptingException(
|
||||||
|
"Could not find any compatible bootstrap!"
|
||||||
|
)
|
||||||
recipes, python_modules, bs = get_recipe_order_and_bootstrap(
|
recipes, python_modules, bs = get_recipe_order_and_bootstrap(
|
||||||
ctx, chosen_order, bs=bs)
|
ctx, chosen_order, bs=bs, blacklist=blacklist
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# check if each requirement has a recipe
|
# check if each requirement has a recipe
|
||||||
recipes = []
|
recipes = []
|
||||||
python_modules = []
|
python_modules = []
|
||||||
for name in chosen_order:
|
for name in chosen_order:
|
||||||
try:
|
try:
|
||||||
Recipe.get_recipe(name, ctx)
|
recipe = Recipe.get_recipe(name, ctx)
|
||||||
except IOError:
|
python_modules += recipe.python_depends
|
||||||
|
except ValueError:
|
||||||
python_modules.append(name)
|
python_modules.append(name)
|
||||||
else:
|
else:
|
||||||
recipes.append(name)
|
recipes.append(name)
|
||||||
|
|
||||||
|
python_modules = list(set(python_modules))
|
||||||
return recipes, python_modules, bs
|
return recipes, python_modules, bs
|
||||||
|
|
|
@ -44,9 +44,9 @@ class LevelDifferentiatingFormatter(logging.Formatter):
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('p4a')
|
logger = logging.getLogger('p4a')
|
||||||
if not hasattr(logger, 'touched'): # Necessary as importlib reloads
|
# Necessary as importlib reloads this,
|
||||||
# this, which would add a second
|
# which would add a second handler and reset the level
|
||||||
# handler and reset the level
|
if not hasattr(logger, 'touched'):
|
||||||
logger.setLevel(logging.INFO)
|
logger.setLevel(logging.INFO)
|
||||||
logger.touched = True
|
logger.touched = True
|
||||||
ch = logging.StreamHandler(stderr)
|
ch = logging.StreamHandler(stderr)
|
||||||
|
@ -148,8 +148,10 @@ def shprint(command, *args, **kwargs):
|
||||||
kwargs["_bg"] = True
|
kwargs["_bg"] = True
|
||||||
is_critical = kwargs.pop('_critical', False)
|
is_critical = kwargs.pop('_critical', False)
|
||||||
tail_n = kwargs.pop('_tail', None)
|
tail_n = kwargs.pop('_tail', None)
|
||||||
|
full_debug = False
|
||||||
if "P4A_FULL_DEBUG" in os.environ:
|
if "P4A_FULL_DEBUG" in os.environ:
|
||||||
tail_n = 0
|
tail_n = 0
|
||||||
|
full_debug = True
|
||||||
filter_in = kwargs.pop('_filter', None)
|
filter_in = kwargs.pop('_filter', None)
|
||||||
filter_out = kwargs.pop('_filterout', None)
|
filter_out = kwargs.pop('_filterout', None)
|
||||||
if len(logger.handlers) > 1:
|
if len(logger.handlers) > 1:
|
||||||
|
@ -177,11 +179,16 @@ def shprint(command, *args, **kwargs):
|
||||||
if isinstance(line, bytes):
|
if isinstance(line, bytes):
|
||||||
line = line.decode('utf-8', errors='replace')
|
line = line.decode('utf-8', errors='replace')
|
||||||
if logger.level > logging.DEBUG:
|
if logger.level > logging.DEBUG:
|
||||||
|
if full_debug:
|
||||||
|
stdout.write(line)
|
||||||
|
stdout.flush()
|
||||||
|
continue
|
||||||
msg = line.replace(
|
msg = line.replace(
|
||||||
'\n', ' ').replace(
|
'\n', ' ').replace(
|
||||||
'\t', ' ').replace(
|
'\t', ' ').replace(
|
||||||
'\b', ' ').rstrip()
|
'\b', ' ').rstrip()
|
||||||
if msg:
|
if msg:
|
||||||
|
if "CI" not in os.environ:
|
||||||
stdout.write(u'{}\r{}{:<{width}}'.format(
|
stdout.write(u'{}\r{}{:<{width}}'.format(
|
||||||
Err_Style.RESET_ALL, msg_hdr,
|
Err_Style.RESET_ALL, msg_hdr,
|
||||||
shorten_string(msg, msg_width), width=msg_width))
|
shorten_string(msg, msg_width), width=msg_width))
|
||||||
|
|
437
p4a/pythonforandroid/python.py
Executable file
437
p4a/pythonforandroid/python.py
Executable file
|
@ -0,0 +1,437 @@
|
||||||
|
'''
|
||||||
|
This module is kind of special because it contains the base classes used to
|
||||||
|
build our python3 and python2 recipes and his corresponding hostpython recipes.
|
||||||
|
'''
|
||||||
|
|
||||||
|
from os.path import dirname, exists, join
|
||||||
|
from multiprocessing import cpu_count
|
||||||
|
from shutil import copy2
|
||||||
|
from os import environ
|
||||||
|
import subprocess
|
||||||
|
import glob
|
||||||
|
import sh
|
||||||
|
|
||||||
|
from pythonforandroid.recipe import Recipe, TargetPythonRecipe
|
||||||
|
from pythonforandroid.logger import logger, info, shprint
|
||||||
|
from pythonforandroid.util import (
|
||||||
|
current_directory, ensure_dir, walk_valid_filens,
|
||||||
|
BuildInterruptingException, build_platform)
|
||||||
|
|
||||||
|
|
||||||
|
class GuestPythonRecipe(TargetPythonRecipe):
|
||||||
|
'''
|
||||||
|
Class for target python recipes. Sets ctx.python_recipe to point to itself,
|
||||||
|
so as to know later what kind of Python was built or used.
|
||||||
|
|
||||||
|
This base class is used for our main python recipes (python2 and python3)
|
||||||
|
which shares most of the build process.
|
||||||
|
|
||||||
|
.. versionadded:: 0.6.0
|
||||||
|
Refactored from the inclement's python3 recipe with a few changes:
|
||||||
|
|
||||||
|
- Splits the python's build process several methods: :meth:`build_arch`
|
||||||
|
and :meth:`get_recipe_env`.
|
||||||
|
- Adds the attribute :attr:`configure_args`, which has been moved from
|
||||||
|
the method :meth:`build_arch` into a static class variable.
|
||||||
|
- Adds some static class variables used to create the python bundle and
|
||||||
|
modifies the method :meth:`create_python_bundle`, to adapt to the new
|
||||||
|
situation. The added static class variables are:
|
||||||
|
:attr:`stdlib_dir_blacklist`, :attr:`stdlib_filen_blacklist`,
|
||||||
|
:attr:`site_packages_dir_blacklist`and
|
||||||
|
:attr:`site_packages_filen_blacklist`.
|
||||||
|
'''
|
||||||
|
|
||||||
|
MIN_NDK_API = 21
|
||||||
|
'''Sets the minimal ndk api number needed to use the recipe.
|
||||||
|
|
||||||
|
.. warning:: This recipe can be built only against API 21+, so it means
|
||||||
|
that any class which inherits from class:`GuestPythonRecipe` will have
|
||||||
|
this limitation.
|
||||||
|
'''
|
||||||
|
|
||||||
|
from_crystax = False
|
||||||
|
'''True if the python is used from CrystaX, False otherwise (i.e. if
|
||||||
|
it is built by p4a).'''
|
||||||
|
|
||||||
|
configure_args = ()
|
||||||
|
'''The configure arguments needed to build the python recipe. Those are
|
||||||
|
used in method :meth:`build_arch` (if not overwritten like python3crystax's
|
||||||
|
recipe does).
|
||||||
|
|
||||||
|
.. note:: This variable should be properly set in subclass.
|
||||||
|
'''
|
||||||
|
|
||||||
|
stdlib_dir_blacklist = {
|
||||||
|
'__pycache__',
|
||||||
|
'test',
|
||||||
|
'tests',
|
||||||
|
'lib2to3',
|
||||||
|
'ensurepip',
|
||||||
|
'idlelib',
|
||||||
|
'tkinter',
|
||||||
|
}
|
||||||
|
'''The directories that we want to omit for our python bundle'''
|
||||||
|
|
||||||
|
stdlib_filen_blacklist = [
|
||||||
|
'*.py',
|
||||||
|
'*.exe',
|
||||||
|
'*.whl',
|
||||||
|
]
|
||||||
|
'''The file extensions that we want to blacklist for our python bundle'''
|
||||||
|
|
||||||
|
site_packages_dir_blacklist = {
|
||||||
|
'__pycache__',
|
||||||
|
'tests'
|
||||||
|
}
|
||||||
|
'''The directories from site packages dir that we don't want to be included
|
||||||
|
in our python bundle.'''
|
||||||
|
|
||||||
|
site_packages_filen_blacklist = [
|
||||||
|
'*.py'
|
||||||
|
]
|
||||||
|
'''The file extensions from site packages dir that we don't want to be
|
||||||
|
included in our python bundle.'''
|
||||||
|
|
||||||
|
opt_depends = ['sqlite3', 'libffi', 'openssl']
|
||||||
|
'''The optional libraries which we would like to get our python linked'''
|
||||||
|
|
||||||
|
compiled_extension = '.pyc'
|
||||||
|
'''the default extension for compiled python files.
|
||||||
|
|
||||||
|
.. note:: the default extension for compiled python files has been .pyo for
|
||||||
|
python 2.x-3.4 but as of Python 3.5, the .pyo filename extension is no
|
||||||
|
longer used and has been removed in favour of extension .pyc
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self._ctx = None
|
||||||
|
super(GuestPythonRecipe, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_recipe_env(self, arch=None, with_flags_in_cc=True):
|
||||||
|
if self.from_crystax:
|
||||||
|
return super(GuestPythonRecipe, self).get_recipe_env(
|
||||||
|
arch=arch, with_flags_in_cc=with_flags_in_cc)
|
||||||
|
|
||||||
|
env = environ.copy()
|
||||||
|
|
||||||
|
android_host = env['HOSTARCH'] = arch.command_prefix
|
||||||
|
toolchain = '{toolchain_prefix}-{toolchain_version}'.format(
|
||||||
|
toolchain_prefix=self.ctx.toolchain_prefix,
|
||||||
|
toolchain_version=self.ctx.toolchain_version)
|
||||||
|
toolchain = join(self.ctx.ndk_dir, 'toolchains',
|
||||||
|
toolchain, 'prebuilt', build_platform)
|
||||||
|
|
||||||
|
env['CC'] = (
|
||||||
|
'{clang} -target {target} -gcc-toolchain {toolchain}').format(
|
||||||
|
clang=join(self.ctx.ndk_dir, 'toolchains', 'llvm', 'prebuilt',
|
||||||
|
build_platform, 'bin', 'clang'),
|
||||||
|
target=arch.target,
|
||||||
|
toolchain=toolchain)
|
||||||
|
env['AR'] = join(toolchain, 'bin', android_host) + '-ar'
|
||||||
|
env['LD'] = join(toolchain, 'bin', android_host) + '-ld'
|
||||||
|
env['RANLIB'] = join(toolchain, 'bin', android_host) + '-ranlib'
|
||||||
|
env['READELF'] = join(toolchain, 'bin', android_host) + '-readelf'
|
||||||
|
env['STRIP'] = join(toolchain, 'bin', android_host) + '-strip'
|
||||||
|
env['STRIP'] += ' --strip-debug --strip-unneeded'
|
||||||
|
|
||||||
|
env['PATH'] = (
|
||||||
|
'{hostpython_dir}:{old_path}').format(
|
||||||
|
hostpython_dir=self.get_recipe(
|
||||||
|
'host' + self.name, self.ctx).get_path_to_python(),
|
||||||
|
old_path=env['PATH'])
|
||||||
|
|
||||||
|
ndk_flags = (
|
||||||
|
'-fPIC --sysroot={ndk_sysroot} -D__ANDROID_API__={android_api} '
|
||||||
|
'-isystem {ndk_android_host} -I{ndk_include}').format(
|
||||||
|
ndk_sysroot=join(self.ctx.ndk_dir, 'sysroot'),
|
||||||
|
android_api=self.ctx.ndk_api,
|
||||||
|
ndk_android_host=join(
|
||||||
|
self.ctx.ndk_dir, 'sysroot', 'usr', 'include', android_host),
|
||||||
|
ndk_include=join(self.ctx.ndk_dir, 'sysroot', 'usr', 'include'))
|
||||||
|
sysroot = self.ctx.ndk_platform
|
||||||
|
env['CFLAGS'] = env.get('CFLAGS', '') + ' ' + ndk_flags
|
||||||
|
env['CPPFLAGS'] = env.get('CPPFLAGS', '') + ' ' + ndk_flags
|
||||||
|
env['LDFLAGS'] = env.get('LDFLAGS', '') + ' --sysroot={} -L{}'.format(
|
||||||
|
sysroot, join(sysroot, 'usr', 'lib'))
|
||||||
|
|
||||||
|
# Manually add the libs directory, and copy some object
|
||||||
|
# files to the current directory otherwise they aren't
|
||||||
|
# picked up. This seems necessary because the --sysroot
|
||||||
|
# setting in LDFLAGS is overridden by the other flags.
|
||||||
|
# TODO: Work out why this doesn't happen in the original
|
||||||
|
# bpo-30386 Makefile system.
|
||||||
|
logger.warning('Doing some hacky stuff to link properly')
|
||||||
|
lib_dir = join(sysroot, 'usr', 'lib')
|
||||||
|
if arch.arch == 'x86_64':
|
||||||
|
lib_dir = join(sysroot, 'usr', 'lib64')
|
||||||
|
env['LDFLAGS'] += ' -L{}'.format(lib_dir)
|
||||||
|
shprint(sh.cp, join(lib_dir, 'crtbegin_so.o'), './')
|
||||||
|
shprint(sh.cp, join(lib_dir, 'crtend_so.o'), './')
|
||||||
|
|
||||||
|
env['SYSROOT'] = sysroot
|
||||||
|
|
||||||
|
if sh.which('lld') is not None:
|
||||||
|
# Note: The -L. is to fix a bug in python 3.7.
|
||||||
|
# https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=234409
|
||||||
|
env["LDFLAGS"] += ' -L. -fuse-ld=lld'
|
||||||
|
else:
|
||||||
|
logger.warning('lld not found, linking without it. ' +
|
||||||
|
'Consider installing lld if linker errors occur.')
|
||||||
|
|
||||||
|
return env
|
||||||
|
|
||||||
|
def set_libs_flags(self, env, arch):
|
||||||
|
'''Takes care to properly link libraries with python depending on our
|
||||||
|
requirements and the attribute :attr:`opt_depends`.
|
||||||
|
'''
|
||||||
|
def add_flags(include_flags, link_dirs, link_libs):
|
||||||
|
env['CPPFLAGS'] = env.get('CPPFLAGS', '') + include_flags
|
||||||
|
env['LDFLAGS'] = env.get('LDFLAGS', '') + link_dirs
|
||||||
|
env['LIBS'] = env.get('LIBS', '') + link_libs
|
||||||
|
|
||||||
|
if 'sqlite3' in self.ctx.recipe_build_order:
|
||||||
|
info('Activating flags for sqlite3')
|
||||||
|
recipe = Recipe.get_recipe('sqlite3', self.ctx)
|
||||||
|
add_flags(' -I' + recipe.get_build_dir(arch.arch),
|
||||||
|
' -L' + recipe.get_lib_dir(arch), ' -lsqlite3')
|
||||||
|
|
||||||
|
if 'libffi' in self.ctx.recipe_build_order:
|
||||||
|
info('Activating flags for libffi')
|
||||||
|
recipe = Recipe.get_recipe('libffi', self.ctx)
|
||||||
|
# In order to force the correct linkage for our libffi library, we
|
||||||
|
# set the following variable to point where is our libffi.pc file,
|
||||||
|
# because the python build system uses pkg-config to configure it.
|
||||||
|
env['PKG_CONFIG_PATH'] = recipe.get_build_dir(arch.arch)
|
||||||
|
add_flags(' -I' + ' -I'.join(recipe.get_include_dirs(arch)),
|
||||||
|
' -L' + join(recipe.get_build_dir(arch.arch), '.libs'),
|
||||||
|
' -lffi')
|
||||||
|
|
||||||
|
if 'openssl' in self.ctx.recipe_build_order:
|
||||||
|
info('Activating flags for openssl')
|
||||||
|
recipe = Recipe.get_recipe('openssl', self.ctx)
|
||||||
|
add_flags(recipe.include_flags(arch),
|
||||||
|
recipe.link_dirs_flags(arch), recipe.link_libs_flags())
|
||||||
|
return env
|
||||||
|
|
||||||
|
def prebuild_arch(self, arch):
|
||||||
|
super(TargetPythonRecipe, self).prebuild_arch(arch)
|
||||||
|
if self.from_crystax and self.ctx.ndk != 'crystax':
|
||||||
|
raise BuildInterruptingException(
|
||||||
|
'The {} recipe can only be built when using the CrystaX NDK. '
|
||||||
|
'Exiting.'.format(self.name))
|
||||||
|
self.ctx.python_recipe = self
|
||||||
|
|
||||||
|
def build_arch(self, arch):
|
||||||
|
if self.ctx.ndk_api < self.MIN_NDK_API:
|
||||||
|
raise BuildInterruptingException(
|
||||||
|
'Target ndk-api is {}, but the python3 recipe supports only'
|
||||||
|
' {}+'.format(self.ctx.ndk_api, self.MIN_NDK_API))
|
||||||
|
|
||||||
|
recipe_build_dir = self.get_build_dir(arch.arch)
|
||||||
|
|
||||||
|
# Create a subdirectory to actually perform the build
|
||||||
|
build_dir = join(recipe_build_dir, 'android-build')
|
||||||
|
ensure_dir(build_dir)
|
||||||
|
|
||||||
|
# TODO: Get these dynamically, like bpo-30386 does
|
||||||
|
sys_prefix = '/usr/local'
|
||||||
|
sys_exec_prefix = '/usr/local'
|
||||||
|
|
||||||
|
with current_directory(build_dir):
|
||||||
|
env = self.get_recipe_env(arch)
|
||||||
|
env = self.set_libs_flags(env, arch)
|
||||||
|
|
||||||
|
android_build = sh.Command(
|
||||||
|
join(recipe_build_dir,
|
||||||
|
'config.guess'))().stdout.strip().decode('utf-8')
|
||||||
|
|
||||||
|
if not exists('config.status'):
|
||||||
|
shprint(
|
||||||
|
sh.Command(join(recipe_build_dir, 'configure')),
|
||||||
|
*(' '.join(self.configure_args).format(
|
||||||
|
android_host=env['HOSTARCH'],
|
||||||
|
android_build=android_build,
|
||||||
|
prefix=sys_prefix,
|
||||||
|
exec_prefix=sys_exec_prefix)).split(' '),
|
||||||
|
_env=env)
|
||||||
|
|
||||||
|
if not exists('python'):
|
||||||
|
py_version = self.major_minor_version_string
|
||||||
|
if self.major_minor_version_string[0] == '3':
|
||||||
|
py_version += 'm'
|
||||||
|
shprint(sh.make, 'all', '-j', str(cpu_count()),
|
||||||
|
'INSTSONAME=libpython{version}.so'.format(
|
||||||
|
version=py_version), _env=env)
|
||||||
|
|
||||||
|
# TODO: Look into passing the path to pyconfig.h in a
|
||||||
|
# better way, although this is probably acceptable
|
||||||
|
sh.cp('pyconfig.h', join(recipe_build_dir, 'Include'))
|
||||||
|
|
||||||
|
def include_root(self, arch_name):
|
||||||
|
return join(self.get_build_dir(arch_name), 'Include')
|
||||||
|
|
||||||
|
def link_root(self, arch_name):
|
||||||
|
return join(self.get_build_dir(arch_name), 'android-build')
|
||||||
|
|
||||||
|
def compile_python_files(self, dir):
|
||||||
|
'''
|
||||||
|
Compile the python files (recursively) for the python files inside
|
||||||
|
a given folder.
|
||||||
|
|
||||||
|
.. note:: python2 compiles the files into extension .pyo, but in
|
||||||
|
python3, and as of Python 3.5, the .pyo filename extension is no
|
||||||
|
longer used...uses .pyc (https://www.python.org/dev/peps/pep-0488)
|
||||||
|
'''
|
||||||
|
args = [self.ctx.hostpython]
|
||||||
|
if self.ctx.python_recipe.name == 'python3':
|
||||||
|
args += ['-OO', '-m', 'compileall', '-b', '-f', dir]
|
||||||
|
else:
|
||||||
|
args += ['-OO', '-m', 'compileall', '-f', dir]
|
||||||
|
subprocess.call(args)
|
||||||
|
|
||||||
|
def create_python_bundle(self, dirn, arch):
|
||||||
|
"""
|
||||||
|
Create a packaged python bundle in the target directory, by
|
||||||
|
copying all the modules and standard library to the right
|
||||||
|
place.
|
||||||
|
"""
|
||||||
|
# Todo: find a better way to find the build libs folder
|
||||||
|
modules_build_dir = join(
|
||||||
|
self.get_build_dir(arch.arch),
|
||||||
|
'android-build',
|
||||||
|
'build',
|
||||||
|
'lib.linux{}-{}-{}'.format(
|
||||||
|
'2' if self.version[0] == '2' else '',
|
||||||
|
arch.command_prefix.split('-')[0],
|
||||||
|
self.major_minor_version_string
|
||||||
|
))
|
||||||
|
|
||||||
|
# Compile to *.pyc/*.pyo the python modules
|
||||||
|
self.compile_python_files(modules_build_dir)
|
||||||
|
# Compile to *.pyc/*.pyo the standard python library
|
||||||
|
self.compile_python_files(join(self.get_build_dir(arch.arch), 'Lib'))
|
||||||
|
# Compile to *.pyc/*.pyo the other python packages (site-packages)
|
||||||
|
self.compile_python_files(self.ctx.get_python_install_dir())
|
||||||
|
|
||||||
|
# Bundle compiled python modules to a folder
|
||||||
|
modules_dir = join(dirn, 'modules')
|
||||||
|
c_ext = self.compiled_extension
|
||||||
|
ensure_dir(modules_dir)
|
||||||
|
module_filens = (glob.glob(join(modules_build_dir, '*.so')) +
|
||||||
|
glob.glob(join(modules_build_dir, '*' + c_ext)))
|
||||||
|
info("Copy {} files into the bundle".format(len(module_filens)))
|
||||||
|
for filen in module_filens:
|
||||||
|
info(" - copy {}".format(filen))
|
||||||
|
copy2(filen, modules_dir)
|
||||||
|
|
||||||
|
# zip up the standard library
|
||||||
|
stdlib_zip = join(dirn, 'stdlib.zip')
|
||||||
|
with current_directory(join(self.get_build_dir(arch.arch), 'Lib')):
|
||||||
|
stdlib_filens = list(walk_valid_filens(
|
||||||
|
'.', self.stdlib_dir_blacklist, self.stdlib_filen_blacklist))
|
||||||
|
info("Zip {} files into the bundle".format(len(stdlib_filens)))
|
||||||
|
shprint(sh.zip, stdlib_zip, *stdlib_filens)
|
||||||
|
|
||||||
|
# copy the site-packages into place
|
||||||
|
ensure_dir(join(dirn, 'site-packages'))
|
||||||
|
ensure_dir(self.ctx.get_python_install_dir())
|
||||||
|
# TODO: Improve the API around walking and copying the files
|
||||||
|
with current_directory(self.ctx.get_python_install_dir()):
|
||||||
|
filens = list(walk_valid_filens(
|
||||||
|
'.', self.site_packages_dir_blacklist,
|
||||||
|
self.site_packages_filen_blacklist))
|
||||||
|
info("Copy {} files into the site-packages".format(len(filens)))
|
||||||
|
for filen in filens:
|
||||||
|
info(" - copy {}".format(filen))
|
||||||
|
ensure_dir(join(dirn, 'site-packages', dirname(filen)))
|
||||||
|
copy2(filen, join(dirn, 'site-packages', filen))
|
||||||
|
|
||||||
|
# copy the python .so files into place
|
||||||
|
python_build_dir = join(self.get_build_dir(arch.arch),
|
||||||
|
'android-build')
|
||||||
|
python_lib_name = 'libpython' + self.major_minor_version_string
|
||||||
|
if self.major_minor_version_string[0] == '3':
|
||||||
|
python_lib_name += 'm'
|
||||||
|
shprint(sh.cp, join(python_build_dir, python_lib_name + '.so'),
|
||||||
|
join(self.ctx.dist_dir, self.ctx.dist_name, 'libs', arch.arch))
|
||||||
|
|
||||||
|
info('Renaming .so files to reflect cross-compile')
|
||||||
|
self.reduce_object_file_names(join(dirn, 'site-packages'))
|
||||||
|
|
||||||
|
return join(dirn, 'site-packages')
|
||||||
|
|
||||||
|
|
||||||
|
class HostPythonRecipe(Recipe):
|
||||||
|
'''
|
||||||
|
This is the base class for hostpython3 and hostpython2 recipes. This class
|
||||||
|
will take care to do all the work to build a hostpython recipe but, be
|
||||||
|
careful, it is intended to be subclassed because some of the vars needs to
|
||||||
|
be set:
|
||||||
|
|
||||||
|
- :attr:`name`
|
||||||
|
- :attr:`version`
|
||||||
|
|
||||||
|
.. versionadded:: 0.6.0
|
||||||
|
Refactored from the hostpython3's recipe by inclement
|
||||||
|
'''
|
||||||
|
|
||||||
|
name = ''
|
||||||
|
'''The hostpython's recipe name. This should be ``hostpython2`` or
|
||||||
|
``hostpython3``
|
||||||
|
|
||||||
|
.. warning:: This must be set in inherited class.'''
|
||||||
|
|
||||||
|
version = ''
|
||||||
|
'''The hostpython's recipe version.
|
||||||
|
|
||||||
|
.. warning:: This must be set in inherited class.'''
|
||||||
|
|
||||||
|
build_subdir = 'native-build'
|
||||||
|
'''Specify the sub build directory for the hostpython recipe. Defaults
|
||||||
|
to ``native-build``.'''
|
||||||
|
|
||||||
|
url = 'https://www.python.org/ftp/python/{version}/Python-{version}.tgz'
|
||||||
|
'''The default url to download our host python recipe. This url will
|
||||||
|
change depending on the python version set in attribute :attr:`version`.'''
|
||||||
|
|
||||||
|
def get_build_container_dir(self, arch=None):
|
||||||
|
choices = self.check_recipe_choices()
|
||||||
|
dir_name = '-'.join([self.name] + choices)
|
||||||
|
return join(self.ctx.build_dir, 'other_builds', dir_name, 'desktop')
|
||||||
|
|
||||||
|
def get_build_dir(self, arch=None):
|
||||||
|
'''
|
||||||
|
.. note:: Unlike other recipes, the hostpython build dir doesn't
|
||||||
|
depend on the target arch
|
||||||
|
'''
|
||||||
|
return join(self.get_build_container_dir(), self.name)
|
||||||
|
|
||||||
|
def get_path_to_python(self):
|
||||||
|
return join(self.get_build_dir(), self.build_subdir)
|
||||||
|
|
||||||
|
def build_arch(self, arch):
|
||||||
|
recipe_build_dir = self.get_build_dir(arch.arch)
|
||||||
|
|
||||||
|
# Create a subdirectory to actually perform the build
|
||||||
|
build_dir = join(recipe_build_dir, self.build_subdir)
|
||||||
|
ensure_dir(build_dir)
|
||||||
|
|
||||||
|
if not exists(join(build_dir, 'python')):
|
||||||
|
with current_directory(recipe_build_dir):
|
||||||
|
# Configure the build
|
||||||
|
with current_directory(build_dir):
|
||||||
|
if not exists('config.status'):
|
||||||
|
shprint(
|
||||||
|
sh.Command(join(recipe_build_dir, 'configure')))
|
||||||
|
|
||||||
|
# Create the Setup file. This copying from Setup.dist
|
||||||
|
# seems to be the normal and expected procedure.
|
||||||
|
shprint(sh.cp, join('Modules', 'Setup.dist'),
|
||||||
|
join(build_dir, 'Modules', 'Setup'))
|
||||||
|
|
||||||
|
shprint(sh.make, '-j', str(cpu_count()), '-C', build_dir)
|
||||||
|
else:
|
||||||
|
info('Skipping {name} ({version}) build, as it has already '
|
||||||
|
'been completed'.format(name=self.name, version=self.version))
|
||||||
|
|
||||||
|
self.ctx.hostpython = join(build_dir, 'python')
|
|
@ -1,4 +1,4 @@
|
||||||
from os.path import basename, dirname, exists, isdir, isfile, join, realpath
|
from os.path import basename, dirname, exists, isdir, isfile, join, realpath, split
|
||||||
import importlib
|
import importlib
|
||||||
import glob
|
import glob
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
|
@ -12,16 +12,16 @@ import shutil
|
||||||
import fnmatch
|
import fnmatch
|
||||||
from os import listdir, unlink, environ, mkdir, curdir, walk
|
from os import listdir, unlink, environ, mkdir, curdir, walk
|
||||||
from sys import stdout
|
from sys import stdout
|
||||||
|
import time
|
||||||
try:
|
try:
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from pythonforandroid.logger import (logger, info, warning, error, debug, shprint, info_main)
|
from pythonforandroid.logger import (logger, info, warning, debug, shprint, info_main)
|
||||||
from pythonforandroid.util import (urlretrieve, current_directory, ensure_dir)
|
from pythonforandroid.util import (urlretrieve, current_directory, ensure_dir,
|
||||||
|
BuildInterruptingException)
|
||||||
|
|
||||||
# this import is necessary to keep imp.load_source from complaining :)
|
# this import is necessary to keep imp.load_source from complaining :)
|
||||||
|
|
||||||
|
|
||||||
if PY2:
|
if PY2:
|
||||||
import imp
|
import imp
|
||||||
import_recipe = imp.load_source
|
import_recipe = imp.load_source
|
||||||
|
@ -140,13 +140,26 @@ class Recipe(with_metaclass(RecipeMeta)):
|
||||||
else:
|
else:
|
||||||
progression = '{0:.2f}%'.format(
|
progression = '{0:.2f}%'.format(
|
||||||
index * blksize * 100. / float(size))
|
index * blksize * 100. / float(size))
|
||||||
|
if "CI" not in environ:
|
||||||
stdout.write('- Download {}\r'.format(progression))
|
stdout.write('- Download {}\r'.format(progression))
|
||||||
stdout.flush()
|
stdout.flush()
|
||||||
|
|
||||||
if exists(target):
|
if exists(target):
|
||||||
unlink(target)
|
unlink(target)
|
||||||
|
|
||||||
|
# Download item with multiple attempts (for bad connections):
|
||||||
|
attempts = 0
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
urlretrieve(url, target, report_hook)
|
urlretrieve(url, target, report_hook)
|
||||||
|
except OSError as e:
|
||||||
|
attempts += 1
|
||||||
|
if attempts >= 5:
|
||||||
|
raise e
|
||||||
|
stdout.write('Download failed retrying in a second...')
|
||||||
|
time.sleep(1)
|
||||||
|
continue
|
||||||
|
break
|
||||||
return target
|
return target
|
||||||
elif parsed_url.scheme in ('git', 'git+file', 'git+ssh', 'git+http', 'git+https'):
|
elif parsed_url.scheme in ('git', 'git+file', 'git+ssh', 'git+http', 'git+https'):
|
||||||
if isdir(target):
|
if isdir(target):
|
||||||
|
@ -167,28 +180,18 @@ class Recipe(with_metaclass(RecipeMeta)):
|
||||||
shprint(sh.git, 'submodule', 'update', '--recursive')
|
shprint(sh.git, 'submodule', 'update', '--recursive')
|
||||||
return target
|
return target
|
||||||
|
|
||||||
# def get_archive_rootdir(self, filename):
|
def apply_patch(self, filename, arch, build_dir=None):
|
||||||
# if filename.endswith(".tgz") or filename.endswith(".tar.gz") or \
|
|
||||||
# filename.endswith(".tbz2") or filename.endswith(".tar.bz2"):
|
|
||||||
# archive = tarfile.open(filename)
|
|
||||||
# root = archive.next().path.split("/")
|
|
||||||
# return root[0]
|
|
||||||
# elif filename.endswith(".zip"):
|
|
||||||
# with zipfile.ZipFile(filename) as zf:
|
|
||||||
# return dirname(zf.namelist()[0])
|
|
||||||
# else:
|
|
||||||
# print("Error: cannot detect root directory")
|
|
||||||
# print("Unrecognized extension for {}".format(filename))
|
|
||||||
# raise Exception()
|
|
||||||
|
|
||||||
def apply_patch(self, filename, arch):
|
|
||||||
"""
|
"""
|
||||||
Apply a patch from the current recipe directory into the current
|
Apply a patch from the current recipe directory into the current
|
||||||
build directory.
|
build directory.
|
||||||
|
|
||||||
|
.. versionchanged:: 0.6.0
|
||||||
|
Add ability to apply patch from any dir via kwarg `build_dir`'''
|
||||||
"""
|
"""
|
||||||
info("Applying patch {}".format(filename))
|
info("Applying patch {}".format(filename))
|
||||||
|
build_dir = build_dir if build_dir else self.get_build_dir(arch)
|
||||||
filename = join(self.get_recipe_dir(), filename)
|
filename = join(self.get_recipe_dir(), filename)
|
||||||
shprint(sh.patch, "-t", "-d", self.get_build_dir(arch), "-p1",
|
shprint(sh.patch, "-t", "-d", build_dir, "-p1",
|
||||||
"-i", filename, _tail=10)
|
"-i", filename, _tail=10)
|
||||||
|
|
||||||
def copy_file(self, filename, dest):
|
def copy_file(self, filename, dest):
|
||||||
|
@ -206,42 +209,12 @@ class Recipe(with_metaclass(RecipeMeta)):
|
||||||
with open(dest, "ab") as fd:
|
with open(dest, "ab") as fd:
|
||||||
fd.write(data)
|
fd.write(data)
|
||||||
|
|
||||||
# def has_marker(self, marker):
|
|
||||||
# """
|
|
||||||
# Return True if the current build directory has the marker set
|
|
||||||
# """
|
|
||||||
# return exists(join(self.build_dir, ".{}".format(marker)))
|
|
||||||
|
|
||||||
# def set_marker(self, marker):
|
|
||||||
# """
|
|
||||||
# Set a marker info the current build directory
|
|
||||||
# """
|
|
||||||
# with open(join(self.build_dir, ".{}".format(marker)), "w") as fd:
|
|
||||||
# fd.write("ok")
|
|
||||||
|
|
||||||
# def delete_marker(self, marker):
|
|
||||||
# """
|
|
||||||
# Delete a specific marker
|
|
||||||
# """
|
|
||||||
# try:
|
|
||||||
# unlink(join(self.build_dir, ".{}".format(marker)))
|
|
||||||
# except:
|
|
||||||
# pass
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
'''The name of the recipe, the same as the folder containing it.'''
|
'''The name of the recipe, the same as the folder containing it.'''
|
||||||
modname = self.__class__.__module__
|
modname = self.__class__.__module__
|
||||||
return modname.split(".", 2)[-1]
|
return modname.split(".", 2)[-1]
|
||||||
|
|
||||||
# @property
|
|
||||||
# def archive_fn(self):
|
|
||||||
# bfn = basename(self.url.format(version=self.version))
|
|
||||||
# fn = "{}/{}-{}".format(
|
|
||||||
# self.ctx.cache_dir,
|
|
||||||
# self.name, bfn)
|
|
||||||
# return fn
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filtered_archs(self):
|
def filtered_archs(self):
|
||||||
'''Return archs of self.ctx that are valid build archs
|
'''Return archs of self.ctx that are valid build archs
|
||||||
|
@ -269,6 +242,12 @@ class Recipe(with_metaclass(RecipeMeta)):
|
||||||
recipes.append(recipe)
|
recipes.append(recipe)
|
||||||
return sorted(recipes)
|
return sorted(recipes)
|
||||||
|
|
||||||
|
def get_opt_depends_in_list(self, recipes):
|
||||||
|
'''Given a list of recipe names, returns those that are also in
|
||||||
|
self.opt_depends.
|
||||||
|
'''
|
||||||
|
return [recipe for recipe in recipes if recipe in self.opt_depends]
|
||||||
|
|
||||||
def get_build_container_dir(self, arch):
|
def get_build_container_dir(self, arch):
|
||||||
'''Given the arch name, returns the directory where it will be
|
'''Given the arch name, returns the directory where it will be
|
||||||
built.
|
built.
|
||||||
|
@ -277,7 +256,8 @@ class Recipe(with_metaclass(RecipeMeta)):
|
||||||
alternative or optional dependencies are being built.
|
alternative or optional dependencies are being built.
|
||||||
'''
|
'''
|
||||||
dir_name = self.get_dir_name()
|
dir_name = self.get_dir_name()
|
||||||
return join(self.ctx.build_dir, 'other_builds', dir_name, arch)
|
return join(self.ctx.build_dir, 'other_builds',
|
||||||
|
dir_name, '{}__ndk_target_{}'.format(arch, self.ctx.ndk_api))
|
||||||
|
|
||||||
def get_dir_name(self):
|
def get_dir_name(self):
|
||||||
choices = self.check_recipe_choices()
|
choices = self.check_recipe_choices()
|
||||||
|
@ -410,24 +390,20 @@ class Recipe(with_metaclass(RecipeMeta)):
|
||||||
try:
|
try:
|
||||||
sh.unzip(extraction_filename)
|
sh.unzip(extraction_filename)
|
||||||
except (sh.ErrorReturnCode_1, sh.ErrorReturnCode_2):
|
except (sh.ErrorReturnCode_1, sh.ErrorReturnCode_2):
|
||||||
pass # return code 1 means unzipping had
|
# return code 1 means unzipping had
|
||||||
# warnings but did complete,
|
# warnings but did complete,
|
||||||
# apparently happens sometimes with
|
# apparently happens sometimes with
|
||||||
# github zips
|
# github zips
|
||||||
|
pass
|
||||||
import zipfile
|
import zipfile
|
||||||
fileh = zipfile.ZipFile(extraction_filename, 'r')
|
fileh = zipfile.ZipFile(extraction_filename, 'r')
|
||||||
root_directory = fileh.filelist[0].filename.split('/')[0]
|
root_directory = fileh.filelist[0].filename.split('/')[0]
|
||||||
if root_directory != basename(directory_name):
|
if root_directory != basename(directory_name):
|
||||||
shprint(sh.mv, root_directory, directory_name)
|
shprint(sh.mv, root_directory, directory_name)
|
||||||
elif (extraction_filename.endswith('.tar.gz') or
|
elif extraction_filename.endswith(
|
||||||
extraction_filename.endswith('.tgz') or
|
('.tar.gz', '.tgz', '.tar.bz2', '.tbz2', '.tar.xz', '.txz')):
|
||||||
extraction_filename.endswith('.tar.bz2') or
|
|
||||||
extraction_filename.endswith('.tbz2') or
|
|
||||||
extraction_filename.endswith('.tar.xz') or
|
|
||||||
extraction_filename.endswith('.txz')):
|
|
||||||
sh.tar('xf', extraction_filename)
|
sh.tar('xf', extraction_filename)
|
||||||
root_directory = shprint(
|
root_directory = sh.tar('tf', extraction_filename).stdout.decode(
|
||||||
sh.tar, 'tf', extraction_filename).stdout.decode(
|
|
||||||
'utf-8').split('\n')[0].split('/')[0]
|
'utf-8').split('\n')[0].split('/')[0]
|
||||||
if root_directory != directory_name:
|
if root_directory != directory_name:
|
||||||
shprint(sh.mv, root_directory, directory_name)
|
shprint(sh.mv, root_directory, directory_name)
|
||||||
|
@ -450,12 +426,12 @@ class Recipe(with_metaclass(RecipeMeta)):
|
||||||
else:
|
else:
|
||||||
info('{} is already unpacked, skipping'.format(self.name))
|
info('{} is already unpacked, skipping'.format(self.name))
|
||||||
|
|
||||||
def get_recipe_env(self, arch=None, with_flags_in_cc=True):
|
def get_recipe_env(self, arch=None, with_flags_in_cc=True, clang=False):
|
||||||
"""Return the env specialized for the recipe
|
"""Return the env specialized for the recipe
|
||||||
"""
|
"""
|
||||||
if arch is None:
|
if arch is None:
|
||||||
arch = self.filtered_archs[0]
|
arch = self.filtered_archs[0]
|
||||||
return arch.get_env(with_flags_in_cc=with_flags_in_cc)
|
return arch.get_env(with_flags_in_cc=with_flags_in_cc, clang=clang)
|
||||||
|
|
||||||
def prebuild_arch(self, arch):
|
def prebuild_arch(self, arch):
|
||||||
'''Run any pre-build tasks for the Recipe. By default, this checks if
|
'''Run any pre-build tasks for the Recipe. By default, this checks if
|
||||||
|
@ -471,8 +447,11 @@ class Recipe(with_metaclass(RecipeMeta)):
|
||||||
build_dir = self.get_build_dir(arch.arch)
|
build_dir = self.get_build_dir(arch.arch)
|
||||||
return exists(join(build_dir, '.patched'))
|
return exists(join(build_dir, '.patched'))
|
||||||
|
|
||||||
def apply_patches(self, arch):
|
def apply_patches(self, arch, build_dir=None):
|
||||||
'''Apply any patches for the Recipe.'''
|
'''Apply any patches for the Recipe.
|
||||||
|
|
||||||
|
.. versionchanged:: 0.6.0
|
||||||
|
Add ability to apply patches from any dir via kwarg `build_dir`'''
|
||||||
if self.patches:
|
if self.patches:
|
||||||
info_main('Applying patches for {}[{}]'
|
info_main('Applying patches for {}[{}]'
|
||||||
.format(self.name, arch.arch))
|
.format(self.name, arch.arch))
|
||||||
|
@ -481,6 +460,7 @@ class Recipe(with_metaclass(RecipeMeta)):
|
||||||
info_main('{} already patched, skipping'.format(self.name))
|
info_main('{} already patched, skipping'.format(self.name))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
build_dir = build_dir if build_dir else self.get_build_dir(arch.arch)
|
||||||
for patch in self.patches:
|
for patch in self.patches:
|
||||||
if isinstance(patch, (tuple, list)):
|
if isinstance(patch, (tuple, list)):
|
||||||
patch, patch_check = patch
|
patch, patch_check = patch
|
||||||
|
@ -489,9 +469,9 @@ class Recipe(with_metaclass(RecipeMeta)):
|
||||||
|
|
||||||
self.apply_patch(
|
self.apply_patch(
|
||||||
patch.format(version=self.version, arch=arch.arch),
|
patch.format(version=self.version, arch=arch.arch),
|
||||||
arch.arch)
|
arch.arch, build_dir=build_dir)
|
||||||
|
|
||||||
shprint(sh.touch, join(self.get_build_dir(arch.arch), '.patched'))
|
shprint(sh.touch, join(build_dir, '.patched'))
|
||||||
|
|
||||||
def should_build(self, arch):
|
def should_build(self, arch):
|
||||||
'''Should perform any necessary test and return True only if it needs
|
'''Should perform any necessary test and return True only if it needs
|
||||||
|
@ -547,8 +527,8 @@ class Recipe(with_metaclass(RecipeMeta)):
|
||||||
if exists(base_dir):
|
if exists(base_dir):
|
||||||
dirs.append(base_dir)
|
dirs.append(base_dir)
|
||||||
if not dirs:
|
if not dirs:
|
||||||
warning(('Attempted to clean build for {} but found no existing '
|
warning('Attempted to clean build for {} but found no existing '
|
||||||
'build dirs').format(self.name))
|
'build dirs'.format(self.name))
|
||||||
|
|
||||||
for directory in dirs:
|
for directory in dirs:
|
||||||
if exists(directory):
|
if exists(directory):
|
||||||
|
@ -595,6 +575,7 @@ class Recipe(with_metaclass(RecipeMeta)):
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_recipe(cls, name, ctx):
|
def get_recipe(cls, name, ctx):
|
||||||
'''Returns the Recipe with the given name, if it exists.'''
|
'''Returns the Recipe with the given name, if it exists.'''
|
||||||
|
name = name.lower()
|
||||||
if not hasattr(cls, "recipes"):
|
if not hasattr(cls, "recipes"):
|
||||||
cls.recipes = {}
|
cls.recipes = {}
|
||||||
if name in cls.recipes:
|
if name in cls.recipes:
|
||||||
|
@ -602,20 +583,28 @@ class Recipe(with_metaclass(RecipeMeta)):
|
||||||
|
|
||||||
recipe_file = None
|
recipe_file = None
|
||||||
for recipes_dir in cls.recipe_dirs(ctx):
|
for recipes_dir in cls.recipe_dirs(ctx):
|
||||||
recipe_file = join(recipes_dir, name, '__init__.py')
|
if not exists(recipes_dir):
|
||||||
|
continue
|
||||||
|
# Find matching folder (may differ in case):
|
||||||
|
for subfolder in listdir(recipes_dir):
|
||||||
|
if subfolder.lower() == name:
|
||||||
|
recipe_file = join(recipes_dir, subfolder, '__init__.py')
|
||||||
if exists(recipe_file):
|
if exists(recipe_file):
|
||||||
|
name = subfolder # adapt to actual spelling
|
||||||
break
|
break
|
||||||
recipe_file = None
|
recipe_file = None
|
||||||
|
if recipe_file is not None:
|
||||||
|
break
|
||||||
|
|
||||||
if not recipe_file:
|
if not recipe_file:
|
||||||
raise IOError('Recipe does not exist: {}'.format(name))
|
raise ValueError('Recipe does not exist: {}'.format(name))
|
||||||
|
|
||||||
mod = import_recipe('pythonforandroid.recipes.{}'.format(name), recipe_file)
|
mod = import_recipe('pythonforandroid.recipes.{}'.format(name), recipe_file)
|
||||||
if len(logger.handlers) > 1:
|
if len(logger.handlers) > 1:
|
||||||
logger.removeHandler(logger.handlers[1])
|
logger.removeHandler(logger.handlers[1])
|
||||||
recipe = mod.recipe
|
recipe = mod.recipe
|
||||||
recipe.ctx = ctx
|
recipe.ctx = ctx
|
||||||
cls.recipes[name] = recipe
|
cls.recipes[name.lower()] = recipe
|
||||||
return recipe
|
return recipe
|
||||||
|
|
||||||
|
|
||||||
|
@ -626,8 +615,8 @@ class IncludedFilesBehaviour(object):
|
||||||
|
|
||||||
def prepare_build_dir(self, arch):
|
def prepare_build_dir(self, arch):
|
||||||
if self.src_filename is None:
|
if self.src_filename is None:
|
||||||
print('IncludedFilesBehaviour failed: no src_filename specified')
|
raise BuildInterruptingException(
|
||||||
exit(1)
|
'IncludedFilesBehaviour failed: no src_filename specified')
|
||||||
shprint(sh.rm, '-rf', self.get_build_dir(arch))
|
shprint(sh.rm, '-rf', self.get_build_dir(arch))
|
||||||
shprint(sh.cp, '-a', join(self.get_recipe_dir(), self.src_filename),
|
shprint(sh.cp, '-a', join(self.get_recipe_dir(), self.src_filename),
|
||||||
self.get_build_dir(arch))
|
self.get_build_dir(arch))
|
||||||
|
@ -640,6 +629,9 @@ class BootstrapNDKRecipe(Recipe):
|
||||||
|
|
||||||
To build an NDK project which is not part of the bootstrap, see
|
To build an NDK project which is not part of the bootstrap, see
|
||||||
:class:`~pythonforandroid.recipe.NDKRecipe`.
|
:class:`~pythonforandroid.recipe.NDKRecipe`.
|
||||||
|
|
||||||
|
To link with python, call the method :meth:`get_recipe_env`
|
||||||
|
with the kwarg *with_python=True*.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
dir_name = None # The name of the recipe build folder in the jni dir
|
dir_name = None # The name of the recipe build folder in the jni dir
|
||||||
|
@ -656,6 +648,20 @@ class BootstrapNDKRecipe(Recipe):
|
||||||
def get_jni_dir(self):
|
def get_jni_dir(self):
|
||||||
return join(self.ctx.bootstrap.build_dir, 'jni')
|
return join(self.ctx.bootstrap.build_dir, 'jni')
|
||||||
|
|
||||||
|
def get_recipe_env(self, arch=None, with_flags_in_cc=True, with_python=False):
|
||||||
|
env = super(BootstrapNDKRecipe, self).get_recipe_env(
|
||||||
|
arch, with_flags_in_cc)
|
||||||
|
if not with_python:
|
||||||
|
return env
|
||||||
|
|
||||||
|
env['PYTHON_INCLUDE_ROOT'] = self.ctx.python_recipe.include_root(arch.arch)
|
||||||
|
env['PYTHON_LINK_ROOT'] = self.ctx.python_recipe.link_root(arch.arch)
|
||||||
|
env['EXTRA_LDLIBS'] = ' -lpython{}'.format(
|
||||||
|
self.ctx.python_recipe.major_minor_version_string)
|
||||||
|
if 'python3' in self.ctx.python_recipe.name:
|
||||||
|
env['EXTRA_LDLIBS'] += 'm'
|
||||||
|
return env
|
||||||
|
|
||||||
|
|
||||||
class NDKRecipe(Recipe):
|
class NDKRecipe(Recipe):
|
||||||
'''A recipe class for any NDK project not included in the bootstrap.'''
|
'''A recipe class for any NDK project not included in the bootstrap.'''
|
||||||
|
@ -682,7 +688,13 @@ class NDKRecipe(Recipe):
|
||||||
|
|
||||||
env = self.get_recipe_env(arch)
|
env = self.get_recipe_env(arch)
|
||||||
with current_directory(self.get_build_dir(arch.arch)):
|
with current_directory(self.get_build_dir(arch.arch)):
|
||||||
shprint(sh.ndk_build, 'V=1', 'APP_ABI=' + arch.arch, *extra_args, _env=env)
|
shprint(
|
||||||
|
sh.ndk_build,
|
||||||
|
'V=1',
|
||||||
|
'APP_PLATFORM=android-' + str(self.ctx.ndk_api),
|
||||||
|
'APP_ABI=' + arch.arch,
|
||||||
|
*extra_args, _env=env
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PythonRecipe(Recipe):
|
class PythonRecipe(Recipe):
|
||||||
|
@ -711,6 +723,13 @@ class PythonRecipe(Recipe):
|
||||||
setup_extra_args = []
|
setup_extra_args = []
|
||||||
'''List of extra arugments to pass to setup.py'''
|
'''List of extra arugments to pass to setup.py'''
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(PythonRecipe, self).__init__(*args, **kwargs)
|
||||||
|
depends = self.depends
|
||||||
|
depends.append(('python2', 'python2legacy', 'python3', 'python3crystax'))
|
||||||
|
depends = list(set(depends))
|
||||||
|
self.depends = depends
|
||||||
|
|
||||||
def clean_build(self, arch=None):
|
def clean_build(self, arch=None):
|
||||||
super(PythonRecipe, self).clean_build(arch=arch)
|
super(PythonRecipe, self).clean_build(arch=arch)
|
||||||
name = self.folder_name
|
name = self.folder_name
|
||||||
|
@ -726,14 +745,12 @@ class PythonRecipe(Recipe):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def real_hostpython_location(self):
|
def real_hostpython_location(self):
|
||||||
if 'hostpython2' in self.ctx.recipe_build_order:
|
host_name = 'host{}'.format(self.ctx.python_recipe.name)
|
||||||
return join(
|
host_build = Recipe.get_recipe(host_name, self.ctx).get_build_dir()
|
||||||
Recipe.get_recipe('hostpython2', self.ctx).get_build_dir(),
|
if host_name in ['hostpython2', 'hostpython3']:
|
||||||
'hostpython')
|
return join(host_build, 'native-build', 'python')
|
||||||
elif 'hostpython3crystax' in self.ctx.recipe_build_order:
|
elif host_name in ['hostpython3crystax', 'hostpython2legacy']:
|
||||||
return join(
|
return join(host_build, 'hostpython')
|
||||||
Recipe.get_recipe('hostpython3crystax', self.ctx).get_build_dir(),
|
|
||||||
'hostpython')
|
|
||||||
else:
|
else:
|
||||||
python_recipe = self.ctx.python_recipe
|
python_recipe = self.ctx.python_recipe
|
||||||
return 'python{}'.format(python_recipe.version)
|
return 'python{}'.format(python_recipe.version)
|
||||||
|
@ -757,17 +774,28 @@ class PythonRecipe(Recipe):
|
||||||
|
|
||||||
env['PYTHONNOUSERSITE'] = '1'
|
env['PYTHONNOUSERSITE'] = '1'
|
||||||
|
|
||||||
|
# Set the LANG, this isn't usually important but is a better default
|
||||||
|
# as it occasionally matters how Python e.g. reads files
|
||||||
|
env['LANG'] = "en_GB.UTF-8"
|
||||||
|
|
||||||
if not self.call_hostpython_via_targetpython:
|
if not self.call_hostpython_via_targetpython:
|
||||||
# sets python headers/linkages...depending on python's recipe
|
# sets python headers/linkages...depending on python's recipe
|
||||||
|
python_name = self.ctx.python_recipe.name
|
||||||
python_version = self.ctx.python_recipe.version
|
python_version = self.ctx.python_recipe.version
|
||||||
python_short_version = '.'.join(python_version.split('.')[:2])
|
python_short_version = '.'.join(python_version.split('.')[:2])
|
||||||
if 'python2' in self.ctx.recipe_build_order:
|
if not self.ctx.python_recipe.from_crystax:
|
||||||
env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
|
env['CFLAGS'] += ' -I{}'.format(
|
||||||
env['CFLAGS'] += ' -I' + env[
|
self.ctx.python_recipe.include_root(arch.arch))
|
||||||
'PYTHON_ROOT'] + '/include/python2.7'
|
env['LDFLAGS'] += ' -L{} -lpython{}'.format(
|
||||||
env['LDFLAGS'] += ' -L' + env['PYTHON_ROOT'] + '/lib' + \
|
self.ctx.python_recipe.link_root(arch.arch),
|
||||||
' -lpython2.7'
|
self.ctx.python_recipe.major_minor_version_string)
|
||||||
elif self.ctx.python_recipe.from_crystax:
|
if python_name == 'python3':
|
||||||
|
env['LDFLAGS'] += 'm'
|
||||||
|
elif python_name == 'python2legacy':
|
||||||
|
env['PYTHON_ROOT'] = join(
|
||||||
|
self.ctx.python_recipe.get_build_dir(
|
||||||
|
arch.arch), 'python-install')
|
||||||
|
else:
|
||||||
ndk_dir_python = join(self.ctx.ndk_dir, 'sources',
|
ndk_dir_python = join(self.ctx.ndk_dir, 'sources',
|
||||||
'python', python_version)
|
'python', python_version)
|
||||||
env['CFLAGS'] += ' -I{} '.format(
|
env['CFLAGS'] += ' -I{} '.format(
|
||||||
|
@ -776,22 +804,15 @@ class PythonRecipe(Recipe):
|
||||||
env['LDFLAGS'] += ' -L{}'.format(
|
env['LDFLAGS'] += ' -L{}'.format(
|
||||||
join(ndk_dir_python, 'libs', arch.arch))
|
join(ndk_dir_python, 'libs', arch.arch))
|
||||||
env['LDFLAGS'] += ' -lpython{}m'.format(python_short_version)
|
env['LDFLAGS'] += ' -lpython{}m'.format(python_short_version)
|
||||||
elif 'python3' in self.ctx.recipe_build_order:
|
|
||||||
# This headers are unused cause python3 recipe was removed
|
|
||||||
# TODO: should be reviewed when python3 recipe added
|
|
||||||
env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
|
|
||||||
env['CFLAGS'] += ' -I' + env[
|
|
||||||
'PYTHON_ROOT'] + '/include/python{}m'.format(
|
|
||||||
python_short_version)
|
|
||||||
env['LDFLAGS'] += ' -L' + env['PYTHON_ROOT'] + '/lib' + \
|
|
||||||
' -lpython{}m'.format(
|
|
||||||
python_short_version)
|
|
||||||
hppath = []
|
hppath = []
|
||||||
hppath.append(join(dirname(self.hostpython_location), 'Lib'))
|
hppath.append(join(dirname(self.hostpython_location), 'Lib'))
|
||||||
hppath.append(join(hppath[0], 'site-packages'))
|
hppath.append(join(hppath[0], 'site-packages'))
|
||||||
builddir = join(dirname(self.hostpython_location), 'build')
|
builddir = join(dirname(self.hostpython_location), 'build')
|
||||||
|
if exists(builddir):
|
||||||
hppath += [join(builddir, d) for d in listdir(builddir)
|
hppath += [join(builddir, d) for d in listdir(builddir)
|
||||||
if isdir(join(builddir, d))]
|
if isdir(join(builddir, d))]
|
||||||
|
if len(hppath) > 0:
|
||||||
if 'PYTHONPATH' in env:
|
if 'PYTHONPATH' in env:
|
||||||
env['PYTHONPATH'] = ':'.join(hppath + [env['PYTHONPATH']])
|
env['PYTHONPATH'] = ':'.join(hppath + [env['PYTHONPATH']])
|
||||||
else:
|
else:
|
||||||
|
@ -826,7 +847,7 @@ class PythonRecipe(Recipe):
|
||||||
with current_directory(self.get_build_dir(arch.arch)):
|
with current_directory(self.get_build_dir(arch.arch)):
|
||||||
hostpython = sh.Command(self.hostpython_location)
|
hostpython = sh.Command(self.hostpython_location)
|
||||||
|
|
||||||
if self.ctx.python_recipe.from_crystax:
|
if self.ctx.python_recipe.name != 'python2legacy':
|
||||||
hpenv = env.copy()
|
hpenv = env.copy()
|
||||||
shprint(hostpython, 'setup.py', 'install', '-O2',
|
shprint(hostpython, 'setup.py', 'install', '-O2',
|
||||||
'--root={}'.format(self.ctx.get_python_install_dir()),
|
'--root={}'.format(self.ctx.get_python_install_dir()),
|
||||||
|
@ -835,13 +856,11 @@ class PythonRecipe(Recipe):
|
||||||
elif self.call_hostpython_via_targetpython:
|
elif self.call_hostpython_via_targetpython:
|
||||||
shprint(hostpython, 'setup.py', 'install', '-O2', _env=env,
|
shprint(hostpython, 'setup.py', 'install', '-O2', _env=env,
|
||||||
*self.setup_extra_args)
|
*self.setup_extra_args)
|
||||||
else:
|
else: # python2legacy
|
||||||
hppath = join(dirname(self.hostpython_location), 'Lib',
|
hppath = join(dirname(self.hostpython_location), 'Lib', 'site-packages')
|
||||||
'site-packages')
|
|
||||||
hpenv = env.copy()
|
hpenv = env.copy()
|
||||||
if 'PYTHONPATH' in hpenv:
|
if 'PYTHONPATH' in hpenv:
|
||||||
hpenv['PYTHONPATH'] = ':'.join([hppath] +
|
hpenv['PYTHONPATH'] = ':'.join([hppath] + hpenv['PYTHONPATH'].split(':'))
|
||||||
hpenv['PYTHONPATH'].split(':'))
|
|
||||||
else:
|
else:
|
||||||
hpenv['PYTHONPATH'] = hppath
|
hpenv['PYTHONPATH'] = hppath
|
||||||
shprint(hostpython, 'setup.py', 'install', '-O2',
|
shprint(hostpython, 'setup.py', 'install', '-O2',
|
||||||
|
@ -920,12 +939,14 @@ class CppCompiledComponentsPythonRecipe(CompiledComponentsPythonRecipe):
|
||||||
arch_noeabi=arch.arch.replace('eabi', '')
|
arch_noeabi=arch.arch.replace('eabi', '')
|
||||||
)
|
)
|
||||||
env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
|
env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
|
||||||
env['CFLAGS'] += " -I{ctx.ndk_dir}/platforms/android-{ctx.android_api}/arch-{arch_noeabi}/usr/include" \
|
env['CFLAGS'] += (
|
||||||
" -I{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/include" \
|
" -I{ctx.ndk_dir}/platforms/android-{ctx.android_api}/arch-{arch_noeabi}/usr/include" +
|
||||||
" -I{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/libs/{arch.arch}/include".format(**keys)
|
" -I{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/include" +
|
||||||
|
" -I{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/libs/{arch.arch}/include").format(**keys)
|
||||||
env['CXXFLAGS'] = env['CFLAGS'] + ' -frtti -fexceptions'
|
env['CXXFLAGS'] = env['CFLAGS'] + ' -frtti -fexceptions'
|
||||||
env['LDFLAGS'] += " -L{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/libs/{arch.arch}" \
|
env['LDFLAGS'] += (
|
||||||
" -lgnustl_shared".format(**keys)
|
" -L{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/libs/{arch.arch}" +
|
||||||
|
" -lgnustl_shared").format(**keys)
|
||||||
|
|
||||||
return env
|
return env
|
||||||
|
|
||||||
|
@ -949,7 +970,7 @@ class CythonRecipe(PythonRecipe):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(CythonRecipe, self).__init__(*args, **kwargs)
|
super(CythonRecipe, self).__init__(*args, **kwargs)
|
||||||
depends = self.depends
|
depends = self.depends
|
||||||
depends.append(('python2', 'python3crystax'))
|
depends.append(('python2', 'python2legacy', 'python3', 'python3crystax'))
|
||||||
depends = list(set(depends))
|
depends = list(set(depends))
|
||||||
self.depends = depends
|
self.depends = depends
|
||||||
|
|
||||||
|
@ -966,20 +987,10 @@ class CythonRecipe(PythonRecipe):
|
||||||
|
|
||||||
env = self.get_recipe_env(arch)
|
env = self.get_recipe_env(arch)
|
||||||
|
|
||||||
if self.ctx.python_recipe.from_crystax:
|
|
||||||
command = sh.Command('python{}'.format(self.ctx.python_recipe.version))
|
|
||||||
site_packages_dirs = command(
|
|
||||||
'-c', 'import site; print("\\n".join(site.getsitepackages()))')
|
|
||||||
site_packages_dirs = site_packages_dirs.stdout.decode('utf-8').split('\n')
|
|
||||||
if 'PYTHONPATH' in env:
|
|
||||||
env['PYTHONPATH'] = env['PYTHONPATH'] + ':{}'.format(':'.join(site_packages_dirs))
|
|
||||||
else:
|
|
||||||
env['PYTHONPATH'] = ':'.join(site_packages_dirs)
|
|
||||||
|
|
||||||
with current_directory(self.get_build_dir(arch.arch)):
|
with current_directory(self.get_build_dir(arch.arch)):
|
||||||
hostpython = sh.Command(self.ctx.hostpython)
|
hostpython = sh.Command(self.ctx.hostpython)
|
||||||
shprint(hostpython, '-c', 'import sys; print(sys.path)', _env=env)
|
shprint(hostpython, '-c', 'import sys; print(sys.path)', _env=env)
|
||||||
print('cwd is', realpath(curdir))
|
debug('cwd is {}'.format(realpath(curdir)))
|
||||||
info('Trying first build of {} to get cython files: this is '
|
info('Trying first build of {} to get cython files: this is '
|
||||||
'expected to fail'.format(self.name))
|
'expected to fail'.format(self.name))
|
||||||
|
|
||||||
|
@ -1000,14 +1011,19 @@ class CythonRecipe(PythonRecipe):
|
||||||
info('First build appeared to complete correctly, skipping manual'
|
info('First build appeared to complete correctly, skipping manual'
|
||||||
'cythonising.')
|
'cythonising.')
|
||||||
|
|
||||||
if 'python2' in self.ctx.recipe_build_order:
|
self.strip_object_files(arch, env)
|
||||||
|
|
||||||
|
def strip_object_files(self, arch, env, build_dir=None):
|
||||||
|
if build_dir is None:
|
||||||
|
build_dir = self.get_build_dir(arch.arch)
|
||||||
|
with current_directory(build_dir):
|
||||||
|
info('Stripping object files')
|
||||||
|
if self.ctx.python_recipe.name == 'python2legacy':
|
||||||
info('Stripping object files')
|
info('Stripping object files')
|
||||||
build_lib = glob.glob('./build/lib*')
|
build_lib = glob.glob('./build/lib*')
|
||||||
shprint(sh.find, build_lib[0], '-name', '*.o', '-exec',
|
shprint(sh.find, build_lib[0], '-name', '*.o', '-exec',
|
||||||
env['STRIP'], '{}', ';', _env=env)
|
env['STRIP'], '{}', ';', _env=env)
|
||||||
|
else:
|
||||||
if 'python3crystax' in self.ctx.recipe_build_order:
|
|
||||||
info('Stripping object files')
|
|
||||||
shprint(sh.find, '.', '-iname', '*.so', '-exec',
|
shprint(sh.find, '.', '-iname', '*.so', '-exec',
|
||||||
'/usr/bin/echo', '{}', ';', _env=env)
|
'/usr/bin/echo', '{}', ';', _env=env)
|
||||||
shprint(sh.find, '.', '-iname', '*.so', '-exec',
|
shprint(sh.find, '.', '-iname', '*.so', '-exec',
|
||||||
|
@ -1050,11 +1066,11 @@ class CythonRecipe(PythonRecipe):
|
||||||
if self.ctx.python_recipe.from_crystax:
|
if self.ctx.python_recipe.from_crystax:
|
||||||
env['LDFLAGS'] = (env['LDFLAGS'] +
|
env['LDFLAGS'] = (env['LDFLAGS'] +
|
||||||
' -L{}'.format(join(self.ctx.bootstrap.build_dir, 'libs', arch.arch)))
|
' -L{}'.format(join(self.ctx.bootstrap.build_dir, 'libs', arch.arch)))
|
||||||
# ' -L/home/asandy/.local/share/python-for-android/build/bootstrap_builds/sdl2/libs/armeabi '
|
|
||||||
if self.ctx.python_recipe.from_crystax:
|
if self.ctx.python_recipe.name == 'python2legacy':
|
||||||
env['LDSHARED'] = env['CC'] + ' -shared'
|
|
||||||
else:
|
|
||||||
env['LDSHARED'] = join(self.ctx.root_dir, 'tools', 'liblink.sh')
|
env['LDSHARED'] = join(self.ctx.root_dir, 'tools', 'liblink.sh')
|
||||||
|
else:
|
||||||
|
env['LDSHARED'] = env['CC'] + ' -shared'
|
||||||
# shprint(sh.whereis, env['LDSHARED'], _env=env)
|
# shprint(sh.whereis, env['LDSHARED'], _env=env)
|
||||||
env['LIBLINK'] = 'NOTNONE'
|
env['LIBLINK'] = 'NOTNONE'
|
||||||
env['NDKPLATFORM'] = self.ctx.ndk_platform
|
env['NDKPLATFORM'] = self.ctx.ndk_platform
|
||||||
|
@ -1068,6 +1084,24 @@ class CythonRecipe(PythonRecipe):
|
||||||
env['LIBLINK_PATH'] = liblink_path
|
env['LIBLINK_PATH'] = liblink_path
|
||||||
ensure_dir(liblink_path)
|
ensure_dir(liblink_path)
|
||||||
|
|
||||||
|
# Add crystax-specific site packages:
|
||||||
|
if self.ctx.python_recipe.from_crystax:
|
||||||
|
command = sh.Command('python{}'.format(self.ctx.python_recipe.version))
|
||||||
|
site_packages_dirs = command(
|
||||||
|
'-c', 'import site; print("\\n".join(site.getsitepackages()))')
|
||||||
|
site_packages_dirs = site_packages_dirs.stdout.decode('utf-8').split('\n')
|
||||||
|
if 'PYTHONPATH' in env:
|
||||||
|
env['PYTHONPATH'] = env['PYTHONPATH'] +\
|
||||||
|
':{}'.format(':'.join(site_packages_dirs))
|
||||||
|
else:
|
||||||
|
env['PYTHONPATH'] = ':'.join(site_packages_dirs)
|
||||||
|
while env['PYTHONPATH'].find("::") > 0:
|
||||||
|
env['PYTHONPATH'] = env['PYTHONPATH'].replace("::", ":")
|
||||||
|
if env['PYTHONPATH'].endswith(":"):
|
||||||
|
env['PYTHONPATH'] = env['PYTHONPATH'][:-1]
|
||||||
|
if env['PYTHONPATH'].startswith(":"):
|
||||||
|
env['PYTHONPATH'] = env['PYTHONPATH'][1:]
|
||||||
|
|
||||||
return env
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
@ -1086,19 +1120,44 @@ class TargetPythonRecipe(Recipe):
|
||||||
def prebuild_arch(self, arch):
|
def prebuild_arch(self, arch):
|
||||||
super(TargetPythonRecipe, self).prebuild_arch(arch)
|
super(TargetPythonRecipe, self).prebuild_arch(arch)
|
||||||
if self.from_crystax and self.ctx.ndk != 'crystax':
|
if self.from_crystax and self.ctx.ndk != 'crystax':
|
||||||
error('The {} recipe can only be built when '
|
raise BuildInterruptingException(
|
||||||
|
'The {} recipe can only be built when '
|
||||||
'using the CrystaX NDK. Exiting.'.format(self.name))
|
'using the CrystaX NDK. Exiting.'.format(self.name))
|
||||||
exit(1)
|
|
||||||
self.ctx.python_recipe = self
|
self.ctx.python_recipe = self
|
||||||
|
|
||||||
# @property
|
def include_root(self, arch):
|
||||||
# def ctx(self):
|
'''The root directory from which to include headers.'''
|
||||||
# return self._ctx
|
raise NotImplementedError('Not implemented in TargetPythonRecipe')
|
||||||
|
|
||||||
# @ctx.setter
|
def link_root(self):
|
||||||
# def ctx(self, ctx):
|
raise NotImplementedError('Not implemented in TargetPythonRecipe')
|
||||||
# self._ctx = ctx
|
|
||||||
# ctx.python_recipe = self
|
@property
|
||||||
|
def major_minor_version_string(self):
|
||||||
|
from distutils.version import LooseVersion
|
||||||
|
return '.'.join([str(v) for v in LooseVersion(self.version).version[:2]])
|
||||||
|
|
||||||
|
def create_python_bundle(self, dirn, arch):
|
||||||
|
"""
|
||||||
|
Create a packaged python bundle in the target directory, by
|
||||||
|
copying all the modules and standard library to the right
|
||||||
|
place.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError('{} does not implement create_python_bundle'.format(self))
|
||||||
|
|
||||||
|
def reduce_object_file_names(self, dirn):
|
||||||
|
"""Recursively renames all files named XXX.cpython-...-linux-gnu.so"
|
||||||
|
to "XXX.so", i.e. removing the erroneous architecture name
|
||||||
|
coming from the local system.
|
||||||
|
"""
|
||||||
|
py_so_files = shprint(sh.find, dirn, '-iname', '*.so')
|
||||||
|
filens = py_so_files.stdout.decode('utf-8').split('\n')[:-1]
|
||||||
|
for filen in filens:
|
||||||
|
file_dirname, file_basename = split(filen)
|
||||||
|
parts = file_basename.split('.')
|
||||||
|
if len(parts) <= 2:
|
||||||
|
continue
|
||||||
|
shprint(sh.mv, filen, join(file_dirname, parts[0] + '.so'))
|
||||||
|
|
||||||
|
|
||||||
def md5sum(filen):
|
def md5sum(filen):
|
||||||
|
|
59
p4a/pythonforandroid/recipes/Pillow/__init__.py
Normal file
59
p4a/pythonforandroid/recipes/Pillow/__init__.py
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
from pythonforandroid.recipe import CompiledComponentsPythonRecipe
|
||||||
|
from os.path import join
|
||||||
|
|
||||||
|
|
||||||
|
class PillowRecipe(CompiledComponentsPythonRecipe):
|
||||||
|
|
||||||
|
version = '5.2.0'
|
||||||
|
url = 'https://github.com/python-pillow/Pillow/archive/{version}.tar.gz'
|
||||||
|
site_packages_name = 'Pillow'
|
||||||
|
depends = ['png', 'jpeg', 'freetype', 'setuptools']
|
||||||
|
patches = [join('patches', 'fix-docstring.patch'),
|
||||||
|
join('patches', 'fix-setup.patch')]
|
||||||
|
|
||||||
|
call_hostpython_via_targetpython = False
|
||||||
|
|
||||||
|
def get_recipe_env(self, arch=None, with_flags_in_cc=True):
|
||||||
|
env = super(PillowRecipe, self).get_recipe_env(arch, with_flags_in_cc)
|
||||||
|
|
||||||
|
env['ANDROID_ROOT'] = join(self.ctx.ndk_platform, 'usr')
|
||||||
|
ndk_lib_dir = join(self.ctx.ndk_platform, 'usr', 'lib')
|
||||||
|
ndk_include_dir = join(self.ctx.ndk_dir, 'sysroot', 'usr', 'include')
|
||||||
|
|
||||||
|
png = self.get_recipe('png', self.ctx)
|
||||||
|
png_lib_dir = png.get_lib_dir(arch)
|
||||||
|
png_jni_dir = png.get_jni_dir(arch)
|
||||||
|
|
||||||
|
jpeg = self.get_recipe('jpeg', self.ctx)
|
||||||
|
jpeg_inc_dir = jpeg_lib_dir = jpeg.get_build_dir(arch.arch)
|
||||||
|
|
||||||
|
freetype = self.get_recipe('freetype', self.ctx)
|
||||||
|
free_lib_dir = join(freetype.get_build_dir(arch.arch), 'objs', '.libs')
|
||||||
|
free_inc_dir = join(freetype.get_build_dir(arch.arch), 'include')
|
||||||
|
|
||||||
|
# harfbuzz is a direct dependency of freetype and we need the proper
|
||||||
|
# flags to successfully build the Pillow recipe, so we add them here.
|
||||||
|
harfbuzz = self.get_recipe('harfbuzz', self.ctx)
|
||||||
|
harf_lib_dir = join(harfbuzz.get_build_dir(arch.arch), 'src', '.libs')
|
||||||
|
harf_inc_dir = harfbuzz.get_build_dir(arch.arch)
|
||||||
|
|
||||||
|
env['JPEG_ROOT'] = '{}|{}'.format(jpeg_lib_dir, jpeg_inc_dir)
|
||||||
|
env['FREETYPE_ROOT'] = '{}|{}'.format(free_lib_dir, free_inc_dir)
|
||||||
|
env['ZLIB_ROOT'] = '{}|{}'.format(ndk_lib_dir, ndk_include_dir)
|
||||||
|
|
||||||
|
cflags = ' -I{}'.format(png_jni_dir)
|
||||||
|
cflags += ' -I{} -I{}'.format(harf_inc_dir, join(harf_inc_dir, 'src'))
|
||||||
|
cflags += ' -I{}'.format(free_inc_dir)
|
||||||
|
cflags += ' -I{}'.format(jpeg_inc_dir)
|
||||||
|
cflags += ' -I{}'.format(ndk_include_dir)
|
||||||
|
|
||||||
|
env['LIBS'] = ' -lpng -lfreetype -lharfbuzz -ljpeg -lturbojpeg'
|
||||||
|
|
||||||
|
env['LDFLAGS'] += ' -L{} -L{} -L{} -L{}'.format(
|
||||||
|
png_lib_dir, harf_lib_dir, jpeg_lib_dir, ndk_lib_dir)
|
||||||
|
if cflags not in env['CFLAGS']:
|
||||||
|
env['CFLAGS'] += cflags
|
||||||
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
recipe = PillowRecipe()
|
|
@ -0,0 +1,13 @@
|
||||||
|
diff --git a/src/PIL/__init__.py b/src/PIL/__init__.py
|
||||||
|
index a07280e..6b9fe99 100644
|
||||||
|
--- a/src/PIL/__init__.py
|
||||||
|
+++ b/src/PIL/__init__.py
|
||||||
|
@@ -24,7 +24,7 @@ PILLOW_VERSION = __version__ = _version.__version__
|
||||||
|
|
||||||
|
del _version
|
||||||
|
|
||||||
|
-__doc__ = __doc__.format(__version__) # include version in docstring
|
||||||
|
+__doc__ = ''
|
||||||
|
|
||||||
|
|
||||||
|
_plugins = ['BlpImagePlugin',
|
148
p4a/pythonforandroid/recipes/Pillow/patches/fix-setup.patch
Normal file
148
p4a/pythonforandroid/recipes/Pillow/patches/fix-setup.patch
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
diff --git a/setup.py b/setup.py
|
||||||
|
index 761d552..4ddc598 100755
|
||||||
|
--- a/setup.py
|
||||||
|
+++ b/setup.py
|
||||||
|
@@ -136,12 +136,12 @@ except (ImportError, OSError):
|
||||||
|
|
||||||
|
NAME = 'Pillow'
|
||||||
|
PILLOW_VERSION = get_version()
|
||||||
|
-JPEG_ROOT = None
|
||||||
|
+JPEG_ROOT = tuple(os.environ['JPEG_ROOT'].split('|')) if 'JPEG_ROOT' in os.environ else None
|
||||||
|
JPEG2K_ROOT = None
|
||||||
|
-ZLIB_ROOT = None
|
||||||
|
+ZLIB_ROOT = tuple(os.environ['ZLIB_ROOT'].split('|')) if 'ZLIB_ROOT' in os.environ else None
|
||||||
|
IMAGEQUANT_ROOT = None
|
||||||
|
TIFF_ROOT = None
|
||||||
|
-FREETYPE_ROOT = None
|
||||||
|
+FREETYPE_ROOT = tuple(os.environ['FREETYPE_ROOT'].split('|')) if 'FREETYPE_ROOT' in os.environ else None
|
||||||
|
LCMS_ROOT = None
|
||||||
|
|
||||||
|
|
||||||
|
@@ -194,7 +194,7 @@ class pil_build_ext(build_ext):
|
||||||
|
]
|
||||||
|
|
||||||
|
def initialize_options(self):
|
||||||
|
- self.disable_platform_guessing = None
|
||||||
|
+ self.disable_platform_guessing = True
|
||||||
|
build_ext.initialize_options(self)
|
||||||
|
for x in self.feature:
|
||||||
|
setattr(self, 'disable_%s' % x, None)
|
||||||
|
@@ -466,61 +466,6 @@ class pil_build_ext(build_ext):
|
||||||
|
feature.jpeg = "libjpeg" # alternative name
|
||||||
|
|
||||||
|
feature.openjpeg_version = None
|
||||||
|
- if feature.want('jpeg2000'):
|
||||||
|
- _dbg('Looking for jpeg2000')
|
||||||
|
- best_version = None
|
||||||
|
- best_path = None
|
||||||
|
-
|
||||||
|
- # Find the best version
|
||||||
|
- for directory in self.compiler.include_dirs:
|
||||||
|
- _dbg('Checking for openjpeg-#.# in %s', directory)
|
||||||
|
- try:
|
||||||
|
- listdir = os.listdir(directory)
|
||||||
|
- except Exception:
|
||||||
|
- # WindowsError, FileNotFoundError
|
||||||
|
- continue
|
||||||
|
- for name in listdir:
|
||||||
|
- if name.startswith('openjpeg-') and \
|
||||||
|
- os.path.isfile(os.path.join(directory, name,
|
||||||
|
- 'openjpeg.h')):
|
||||||
|
- _dbg('Found openjpeg.h in %s/%s', (directory, name))
|
||||||
|
- version = tuple(int(x) for x in name[9:].split('.'))
|
||||||
|
- if best_version is None or version > best_version:
|
||||||
|
- best_version = version
|
||||||
|
- best_path = os.path.join(directory, name)
|
||||||
|
- _dbg('Best openjpeg version %s so far in %s',
|
||||||
|
- (best_version, best_path))
|
||||||
|
-
|
||||||
|
- if best_version and _find_library_file(self, 'openjp2'):
|
||||||
|
- # Add the directory to the include path so we can include
|
||||||
|
- # <openjpeg.h> rather than having to cope with the versioned
|
||||||
|
- # include path
|
||||||
|
- # FIXME (melvyn-sopacua):
|
||||||
|
- # At this point it's possible that best_path is already in
|
||||||
|
- # self.compiler.include_dirs. Should investigate how that is
|
||||||
|
- # possible.
|
||||||
|
- _add_directory(self.compiler.include_dirs, best_path, 0)
|
||||||
|
- feature.jpeg2000 = 'openjp2'
|
||||||
|
- feature.openjpeg_version = '.'.join(str(x) for x in best_version)
|
||||||
|
-
|
||||||
|
- if feature.want('imagequant'):
|
||||||
|
- _dbg('Looking for imagequant')
|
||||||
|
- if _find_include_file(self, 'libimagequant.h'):
|
||||||
|
- if _find_library_file(self, "imagequant"):
|
||||||
|
- feature.imagequant = "imagequant"
|
||||||
|
- elif _find_library_file(self, "libimagequant"):
|
||||||
|
- feature.imagequant = "libimagequant"
|
||||||
|
-
|
||||||
|
- if feature.want('tiff'):
|
||||||
|
- _dbg('Looking for tiff')
|
||||||
|
- if _find_include_file(self, 'tiff.h'):
|
||||||
|
- if _find_library_file(self, "tiff"):
|
||||||
|
- feature.tiff = "tiff"
|
||||||
|
- if sys.platform == "win32" and _find_library_file(self, "libtiff"):
|
||||||
|
- feature.tiff = "libtiff"
|
||||||
|
- if (sys.platform == "darwin" and
|
||||||
|
- _find_library_file(self, "libtiff")):
|
||||||
|
- feature.tiff = "libtiff"
|
||||||
|
|
||||||
|
if feature.want('freetype'):
|
||||||
|
_dbg('Looking for freetype')
|
||||||
|
@@ -546,36 +491,6 @@ class pil_build_ext(build_ext):
|
||||||
|
if subdir:
|
||||||
|
_add_directory(self.compiler.include_dirs, subdir, 0)
|
||||||
|
|
||||||
|
- if feature.want('lcms'):
|
||||||
|
- _dbg('Looking for lcms')
|
||||||
|
- if _find_include_file(self, "lcms2.h"):
|
||||||
|
- if _find_library_file(self, "lcms2"):
|
||||||
|
- feature.lcms = "lcms2"
|
||||||
|
- elif _find_library_file(self, "lcms2_static"):
|
||||||
|
- # alternate Windows name.
|
||||||
|
- feature.lcms = "lcms2_static"
|
||||||
|
-
|
||||||
|
- if feature.want('webp'):
|
||||||
|
- _dbg('Looking for webp')
|
||||||
|
- if (_find_include_file(self, "webp/encode.h") and
|
||||||
|
- _find_include_file(self, "webp/decode.h")):
|
||||||
|
- # In Google's precompiled zip it is call "libwebp":
|
||||||
|
- if _find_library_file(self, "webp"):
|
||||||
|
- feature.webp = "webp"
|
||||||
|
- elif _find_library_file(self, "libwebp"):
|
||||||
|
- feature.webp = "libwebp"
|
||||||
|
-
|
||||||
|
- if feature.want('webpmux'):
|
||||||
|
- _dbg('Looking for webpmux')
|
||||||
|
- if (_find_include_file(self, "webp/mux.h") and
|
||||||
|
- _find_include_file(self, "webp/demux.h")):
|
||||||
|
- if (_find_library_file(self, "webpmux") and
|
||||||
|
- _find_library_file(self, "webpdemux")):
|
||||||
|
- feature.webpmux = "webpmux"
|
||||||
|
- if (_find_library_file(self, "libwebpmux") and
|
||||||
|
- _find_library_file(self, "libwebpdemux")):
|
||||||
|
- feature.webpmux = "libwebpmux"
|
||||||
|
-
|
||||||
|
for f in feature:
|
||||||
|
if not getattr(feature, f) and feature.require(f):
|
||||||
|
if f in ('jpeg', 'zlib'):
|
||||||
|
@@ -612,8 +527,6 @@ class pil_build_ext(build_ext):
|
||||||
|
defs.append(("HAVE_LIBTIFF", None))
|
||||||
|
if sys.platform == "win32":
|
||||||
|
libs.extend(["kernel32", "user32", "gdi32"])
|
||||||
|
- if struct.unpack("h", "\0\1".encode('ascii'))[0] == 1:
|
||||||
|
- defs.append(("WORDS_BIGENDIAN", None))
|
||||||
|
|
||||||
|
if sys.platform == "win32" and not (PLATFORM_PYPY or PLATFORM_MINGW):
|
||||||
|
defs.append(("PILLOW_VERSION", '"\\"%s\\""' % PILLOW_VERSION))
|
||||||
|
@@ -658,10 +571,6 @@ class pil_build_ext(build_ext):
|
||||||
|
define_macros=defs))
|
||||||
|
|
||||||
|
tk_libs = ['psapi'] if sys.platform == 'win32' else []
|
||||||
|
- exts.append(Extension("PIL._imagingtk",
|
||||||
|
- ["src/_imagingtk.c", "src/Tk/tkImaging.c"],
|
||||||
|
- include_dirs=['src/Tk'],
|
||||||
|
- libraries=tk_libs))
|
||||||
|
|
||||||
|
exts.append(Extension("PIL._imagingmath", ["src/_imagingmath.c"]))
|
||||||
|
exts.append(Extension("PIL._imagingmorph", ["src/_imagingmorph.c"]))
|
|
@ -1,3 +1,4 @@
|
||||||
|
from __future__ import unicode_literals
|
||||||
from pythonforandroid.recipe import CythonRecipe, IncludedFilesBehaviour
|
from pythonforandroid.recipe import CythonRecipe, IncludedFilesBehaviour
|
||||||
from pythonforandroid.util import current_directory
|
from pythonforandroid.util import current_directory
|
||||||
from pythonforandroid.patching import will_build
|
from pythonforandroid.patching import will_build
|
||||||
|
@ -13,7 +14,8 @@ class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe):
|
||||||
|
|
||||||
src_filename = 'src'
|
src_filename = 'src'
|
||||||
|
|
||||||
depends = [('pygame', 'sdl2', 'genericndkbuild'), ('python2', 'python3crystax')]
|
depends = [('pygame', 'sdl2', 'genericndkbuild'),
|
||||||
|
'pyjnius']
|
||||||
|
|
||||||
config_env = {}
|
config_env = {}
|
||||||
|
|
||||||
|
@ -24,26 +26,35 @@ class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe):
|
||||||
|
|
||||||
def prebuild_arch(self, arch):
|
def prebuild_arch(self, arch):
|
||||||
super(AndroidRecipe, self).prebuild_arch(arch)
|
super(AndroidRecipe, self).prebuild_arch(arch)
|
||||||
|
ctx_bootstrap = self.ctx.bootstrap.name
|
||||||
|
|
||||||
|
# define macros for Cython, C, Python
|
||||||
tpxi = 'DEF {} = {}\n'
|
tpxi = 'DEF {} = {}\n'
|
||||||
th = '#define {} {}\n'
|
th = '#define {} {}\n'
|
||||||
tpy = '{} = {}\n'
|
tpy = '{} = {}\n'
|
||||||
|
|
||||||
bootstrap = bootstrap_name = self.ctx.bootstrap.name
|
# make sure bootstrap name is in unicode
|
||||||
is_sdl2 = bootstrap_name in ('sdl2', 'sdl2python3')
|
if isinstance(ctx_bootstrap, bytes):
|
||||||
|
ctx_bootstrap = ctx_bootstrap.decode('utf-8')
|
||||||
|
bootstrap = bootstrap_name = ctx_bootstrap
|
||||||
|
|
||||||
|
is_sdl2 = bootstrap_name in ('sdl2', 'sdl2python3', 'sdl2_gradle')
|
||||||
is_pygame = bootstrap_name in ('pygame',)
|
is_pygame = bootstrap_name in ('pygame',)
|
||||||
is_webview = bootstrap_name in ('webview',)
|
is_webview = bootstrap_name in ('webview',)
|
||||||
|
|
||||||
if is_sdl2 or is_webview:
|
if is_sdl2 or is_webview:
|
||||||
if is_sdl2:
|
if is_sdl2:
|
||||||
bootstrap = 'sdl2'
|
bootstrap = 'sdl2'
|
||||||
java_ns = 'org.kivy.android'
|
java_ns = u'org.kivy.android'
|
||||||
jni_ns = 'org/kivy/android'
|
jni_ns = u'org/kivy/android'
|
||||||
elif is_pygame:
|
elif is_pygame:
|
||||||
java_ns = 'org.renpy.android'
|
java_ns = u'org.renpy.android'
|
||||||
jni_ns = 'org/renpy/android'
|
jni_ns = u'org/renpy/android'
|
||||||
else:
|
else:
|
||||||
logger.error('unsupported bootstrap for android recipe: {}'.format(bootstrap_name))
|
logger.error((
|
||||||
|
'unsupported bootstrap for android recipe: {}'
|
||||||
|
''.format(bootstrap_name)
|
||||||
|
))
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
|
@ -55,20 +66,28 @@ class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe):
|
||||||
'JNI_NAMESPACE': jni_ns,
|
'JNI_NAMESPACE': jni_ns,
|
||||||
}
|
}
|
||||||
|
|
||||||
with current_directory(self.get_build_dir(arch.arch)):
|
# create config files for Cython, C and Python
|
||||||
with open(join('android', 'config.pxi'), 'w') as fpxi:
|
with (
|
||||||
with open(join('android', 'config.h'), 'w') as fh:
|
current_directory(self.get_build_dir(arch.arch))), (
|
||||||
with open(join('android', 'config.py'), 'w') as fpy:
|
open(join('android', 'config.pxi'), 'w')) as fpxi, (
|
||||||
|
open(join('android', 'config.h'), 'w')) as fh, (
|
||||||
|
open(join('android', 'config.py'), 'w')) as fpy:
|
||||||
|
|
||||||
for key, value in config.items():
|
for key, value in config.items():
|
||||||
fpxi.write(tpxi.format(key, repr(value)))
|
fpxi.write(tpxi.format(key, repr(value)))
|
||||||
fpy.write(tpy.format(key, repr(value)))
|
fpy.write(tpy.format(key, repr(value)))
|
||||||
fh.write(th.format(key, value if isinstance(value, int)
|
|
||||||
else '"{}"'.format(value)))
|
fh.write(th.format(
|
||||||
|
key,
|
||||||
|
value if isinstance(value, int) else '"{}"'.format(value)
|
||||||
|
))
|
||||||
self.config_env[key] = str(value)
|
self.config_env[key] = str(value)
|
||||||
|
|
||||||
if is_sdl2:
|
if is_sdl2:
|
||||||
fh.write('JNIEnv *SDL_AndroidGetJNIEnv(void);\n')
|
fh.write('JNIEnv *SDL_AndroidGetJNIEnv(void);\n')
|
||||||
fh.write('#define SDL_ANDROID_GetJNIEnv SDL_AndroidGetJNIEnv\n')
|
fh.write(
|
||||||
|
'#define SDL_ANDROID_GetJNIEnv SDL_AndroidGetJNIEnv\n'
|
||||||
|
)
|
||||||
elif is_pygame:
|
elif is_pygame:
|
||||||
fh.write('JNIEnv *SDL_ANDROID_GetJNIEnv(void);\n')
|
fh.write('JNIEnv *SDL_ANDROID_GetJNIEnv(void);\n')
|
||||||
|
|
||||||
|
|
|
@ -5,4 +5,4 @@ Android module
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# legacy import
|
# legacy import
|
||||||
from android._android import *
|
from android._android import * # noqa: F401, F403
|
||||||
|
|
|
@ -175,13 +175,13 @@ api_version = autoclass('android.os.Build$VERSION').SDK_INT
|
||||||
version_codes = autoclass('android.os.Build$VERSION_CODES')
|
version_codes = autoclass('android.os.Build$VERSION_CODES')
|
||||||
|
|
||||||
|
|
||||||
python_act = autoclass(JAVA_NAMESPACE + '.PythonActivity')
|
python_act = autoclass(JAVA_NAMESPACE + u'.PythonActivity')
|
||||||
Rect = autoclass('android.graphics.Rect')
|
Rect = autoclass(u'android.graphics.Rect')
|
||||||
mActivity = python_act.mActivity
|
mActivity = python_act.mActivity
|
||||||
if mActivity:
|
if mActivity:
|
||||||
# PyGame backend already has the listener so adding
|
# PyGame backend already has the listener so adding
|
||||||
# one here leads to a crash/too much cpu usage.
|
# one here leads to a crash/too much cpu usage.
|
||||||
# SDL2 now does noe need the listener so there is
|
# SDL2 now does not need the listener so there is
|
||||||
# no point adding a processor intensive layout listenere here.
|
# no point adding a processor intensive layout listenere here.
|
||||||
height = 0
|
height = 0
|
||||||
def get_keyboard_height():
|
def get_keyboard_height():
|
||||||
|
@ -332,7 +332,7 @@ class AndroidBrowser(object):
|
||||||
return open_url(url)
|
return open_url(url)
|
||||||
|
|
||||||
import webbrowser
|
import webbrowser
|
||||||
webbrowser.register('android', AndroidBrowser, None, -1)
|
webbrowser.register('android', AndroidBrowser)
|
||||||
|
|
||||||
cdef extern void android_start_service(char *, char *, char *)
|
cdef extern void android_start_service(char *, char *, char *)
|
||||||
def start_service(title=None, description=None, arg=None):
|
def start_service(title=None, description=None, arg=None):
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
from jnius import PythonJavaClass, java_method, autoclass, cast
|
from jnius import PythonJavaClass, autoclass, java_method
|
||||||
from android.config import JAVA_NAMESPACE, JNI_NAMESPACE
|
from android.config import JAVA_NAMESPACE, JNI_NAMESPACE
|
||||||
|
|
||||||
_activity = autoclass(JAVA_NAMESPACE + '.PythonActivity').mActivity
|
_activity = autoclass(JAVA_NAMESPACE + '.PythonActivity').mActivity
|
||||||
|
|
||||||
_callbacks = {
|
_callbacks = {
|
||||||
'on_new_intent': [],
|
'on_new_intent': [],
|
||||||
'on_activity_result': [] }
|
'on_activity_result': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class NewIntentListener(PythonJavaClass):
|
class NewIntentListener(PythonJavaClass):
|
||||||
__javainterfaces__ = [JNI_NAMESPACE + '/PythonActivity$NewIntentListener']
|
__javainterfaces__ = [JNI_NAMESPACE + '/PythonActivity$NewIntentListener']
|
||||||
|
@ -46,6 +48,7 @@ def bind(**kwargs):
|
||||||
_activity.registerActivityResultListener(listener)
|
_activity.registerActivityResultListener(listener)
|
||||||
_callbacks[event].append(listener)
|
_callbacks[event].append(listener)
|
||||||
|
|
||||||
|
|
||||||
def unbind(**kwargs):
|
def unbind(**kwargs):
|
||||||
for event, callback in kwargs.items():
|
for event, callback in kwargs.items():
|
||||||
if event not in _callbacks:
|
if event not in _callbacks:
|
||||||
|
@ -58,4 +61,3 @@ def unbind(**kwargs):
|
||||||
_activity.unregisterNewIntentListener(listener)
|
_activity.unregisterNewIntentListener(listener)
|
||||||
elif event == 'on_activity_result':
|
elif event == 'on_activity_result':
|
||||||
_activity.unregisterActivityResultListener(listener)
|
_activity.unregisterActivityResultListener(listener)
|
||||||
|
|
||||||
|
|
|
@ -3,5 +3,3 @@ Android Billing API
|
||||||
===================
|
===================
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from android._android_billing import *
|
|
||||||
|
|
|
@ -61,8 +61,8 @@ class BroadcastReceiver(object):
|
||||||
Handler = autoclass('android.os.Handler')
|
Handler = autoclass('android.os.Handler')
|
||||||
self.handlerthread.start()
|
self.handlerthread.start()
|
||||||
self.handler = Handler(self.handlerthread.getLooper())
|
self.handler = Handler(self.handlerthread.getLooper())
|
||||||
self.context.registerReceiver(self.receiver, self.receiver_filter, None,
|
self.context.registerReceiver(
|
||||||
self.handler)
|
self.receiver, self.receiver_filter, None, self.handler)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.context.unregisterReceiver(self.receiver)
|
self.context.unregisterReceiver(self.receiver)
|
||||||
|
@ -76,4 +76,3 @@ class BroadcastReceiver(object):
|
||||||
return PythonService.mService
|
return PythonService.mService
|
||||||
PythonActivity = autoclass(JAVA_NAMESPACE + '.PythonActivity')
|
PythonActivity = autoclass(JAVA_NAMESPACE + '.PythonActivity')
|
||||||
return PythonActivity.mActivity
|
return PythonActivity.mActivity
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
|
||||||
|
from jnius import autoclass
|
||||||
|
|
||||||
|
|
||||||
|
def hide_loading_screen():
|
||||||
|
python_activity = autoclass('org.kivy.android.PythonActivity')
|
||||||
|
python_activity.removeLoadingScreen()
|
|
@ -8,36 +8,45 @@ import os
|
||||||
|
|
||||||
condition = threading.Condition()
|
condition = threading.Condition()
|
||||||
|
|
||||||
|
|
||||||
def periodic():
|
def periodic():
|
||||||
for i in range(0, num_channels):
|
for i in range(0, num_channels):
|
||||||
if i in channels:
|
if i in channels:
|
||||||
channels[i].periodic()
|
channels[i].periodic()
|
||||||
|
|
||||||
|
|
||||||
num_channels = 8
|
num_channels = 8
|
||||||
reserved_channels = 0
|
reserved_channels = 0
|
||||||
|
|
||||||
|
|
||||||
def init(frequency=22050, size=-16, channels=2, buffer=4096):
|
def init(frequency=22050, size=-16, channels=2, buffer=4096):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def pre_init(frequency=22050, size=-16, channels=2, buffersize=4096):
|
def pre_init(frequency=22050, size=-16, channels=2, buffersize=4096):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def quit():
|
def quit():
|
||||||
stop()
|
stop()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def stop():
|
def stop():
|
||||||
for i in range(0, num_channels):
|
for i in range(0, num_channels):
|
||||||
sound.stop(i)
|
sound.stop(i)
|
||||||
|
|
||||||
|
|
||||||
def pause():
|
def pause():
|
||||||
for i in range(0, num_channels):
|
for i in range(0, num_channels):
|
||||||
sound.pause(i)
|
sound.pause(i)
|
||||||
|
|
||||||
|
|
||||||
def unpause():
|
def unpause():
|
||||||
for i in range(0, num_channels):
|
for i in range(0, num_channels):
|
||||||
sound.unpause(i)
|
sound.unpause(i)
|
||||||
|
|
||||||
|
|
||||||
def get_busy():
|
def get_busy():
|
||||||
for i in range(0, num_channels):
|
for i in range(0, num_channels):
|
||||||
if sound.busy(i):
|
if sound.busy(i):
|
||||||
|
@ -45,6 +54,7 @@ def get_busy():
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def fadeout(time):
|
def fadeout(time):
|
||||||
# Fadeout doesn't work - it just immediately stops playback.
|
# Fadeout doesn't work - it just immediately stops playback.
|
||||||
stop()
|
stop()
|
||||||
|
@ -53,17 +63,21 @@ def fadeout(time):
|
||||||
# A map from channel number to Channel object.
|
# A map from channel number to Channel object.
|
||||||
channels = {}
|
channels = {}
|
||||||
|
|
||||||
|
|
||||||
def set_num_channels(count):
|
def set_num_channels(count):
|
||||||
global num_channels
|
global num_channels
|
||||||
num_channels = count
|
num_channels = count
|
||||||
|
|
||||||
|
|
||||||
def get_num_channels(count):
|
def get_num_channels(count):
|
||||||
return num_channels
|
return num_channels
|
||||||
|
|
||||||
|
|
||||||
def set_reserved(count):
|
def set_reserved(count):
|
||||||
global reserved_channels
|
global reserved_channels
|
||||||
reserved_channels = count
|
reserved_channels = count
|
||||||
|
|
||||||
|
|
||||||
def find_channel(force=False):
|
def find_channel(force=False):
|
||||||
|
|
||||||
busy = []
|
busy = []
|
||||||
|
@ -83,6 +97,7 @@ def find_channel(force=False):
|
||||||
|
|
||||||
return busy[0]
|
return busy[0]
|
||||||
|
|
||||||
|
|
||||||
class ChannelImpl(object):
|
class ChannelImpl(object):
|
||||||
|
|
||||||
def __init__(self, id):
|
def __init__(self, id):
|
||||||
|
@ -101,7 +116,6 @@ class ChannelImpl(object):
|
||||||
if self.loop is not None and sound.queue_depth(self.id) < 2:
|
if self.loop is not None and sound.queue_depth(self.id) < 2:
|
||||||
self.queue(self.loop, loops=1)
|
self.queue(self.loop, loops=1)
|
||||||
|
|
||||||
|
|
||||||
def play(self, s, loops=0, maxtime=0, fade_ms=0):
|
def play(self, s, loops=0, maxtime=0, fade_ms=0):
|
||||||
if loops:
|
if loops:
|
||||||
self.loop = s
|
self.loop = s
|
||||||
|
@ -183,6 +197,7 @@ def Channel(n):
|
||||||
sound_serial = 0
|
sound_serial = 0
|
||||||
sounds = {}
|
sounds = {}
|
||||||
|
|
||||||
|
|
||||||
class Sound(object):
|
class Sound(object):
|
||||||
|
|
||||||
def __init__(self, what):
|
def __init__(self, what):
|
||||||
|
@ -196,10 +211,10 @@ class Sound(object):
|
||||||
self.serial = str(sound_serial)
|
self.serial = str(sound_serial)
|
||||||
sound_serial += 1
|
sound_serial += 1
|
||||||
|
|
||||||
if isinstance(what, file):
|
if isinstance(what, file): # noqa F821
|
||||||
self.file = what
|
self.file = what
|
||||||
else:
|
else:
|
||||||
self.file = file(os.path.abspath(what), "rb")
|
self.file = file(os.path.abspath(what), "rb") # noqa F821
|
||||||
|
|
||||||
sounds[self.serial] = self
|
sounds[self.serial] = self
|
||||||
|
|
||||||
|
@ -214,7 +229,6 @@ class Sound(object):
|
||||||
channel.play(self, loops=loops)
|
channel.play(self, loops=loops)
|
||||||
return channel
|
return channel
|
||||||
|
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
for i in range(0, num_channels):
|
for i in range(0, num_channels):
|
||||||
if Channel(i).get_sound() is self:
|
if Channel(i).get_sound() is self:
|
||||||
|
@ -244,9 +258,11 @@ class Sound(object):
|
||||||
def get_length(self):
|
def get_length(self):
|
||||||
return 1.0
|
return 1.0
|
||||||
|
|
||||||
|
|
||||||
music_channel = Channel(256)
|
music_channel = Channel(256)
|
||||||
music_sound = None
|
music_sound = None
|
||||||
|
|
||||||
|
|
||||||
class music(object):
|
class music(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -306,6 +322,3 @@ class music(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def queue(filename):
|
def queue(filename):
|
||||||
return music_channel.queue(Sound(filename))
|
return music_channel.queue(Sound(filename))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
438
p4a/pythonforandroid/recipes/android/src/android/permissions.py
Normal file
438
p4a/pythonforandroid/recipes/android/src/android/permissions.py
Normal file
|
@ -0,0 +1,438 @@
|
||||||
|
|
||||||
|
try:
|
||||||
|
from jnius import autoclass
|
||||||
|
except ImportError:
|
||||||
|
# To allow importing by build/manifest-creating code without
|
||||||
|
# pyjnius being present:
|
||||||
|
def autoclass(item):
|
||||||
|
raise RuntimeError("pyjnius not available")
|
||||||
|
|
||||||
|
|
||||||
|
class Permission:
|
||||||
|
ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER"
|
||||||
|
ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"
|
||||||
|
ACCESS_LOCATION_EXTRA_COMMANDS = (
|
||||||
|
"android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"
|
||||||
|
)
|
||||||
|
ACCESS_NETWORK_STATE = "android.permission.ACCESS_NETWORK_STATE"
|
||||||
|
ACCESS_NOTIFICATION_POLICY = (
|
||||||
|
"android.permission.ACCESS_NOTIFICATION_POLICY"
|
||||||
|
)
|
||||||
|
ACCESS_WIFI_STATE = "android.permission.ACCESS_WIFI_STATE"
|
||||||
|
ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL"
|
||||||
|
ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS"
|
||||||
|
BATTERY_STATS = "android.permission.BATTERY_STATS"
|
||||||
|
BIND_ACCESSIBILITY_SERVICE = (
|
||||||
|
"android.permission.BIND_ACCESSIBILITY_SERVICE"
|
||||||
|
)
|
||||||
|
BIND_AUTOFILL_SERVICE = "android.permission.BIND_AUTOFILL_SERVICE"
|
||||||
|
BIND_CARRIER_MESSAGING_SERVICE = ( # note: deprecated in api 23+
|
||||||
|
"android.permission.BIND_CARRIER_MESSAGING_SERVICE"
|
||||||
|
)
|
||||||
|
BIND_CARRIER_SERVICES = ( # replaces BIND_CARRIER_MESSAGING_SERVICE
|
||||||
|
"android.permission.BIND_CARRIER_SERVICES"
|
||||||
|
)
|
||||||
|
BIND_CHOOSER_TARGET_SERVICE = (
|
||||||
|
"android.permission.BIND_CHOOSER_TARGET_SERVICE"
|
||||||
|
)
|
||||||
|
BIND_CONDITION_PROVIDER_SERVICE = (
|
||||||
|
"android.permission.BIND_CONDITION_PROVIDER_SERVICE"
|
||||||
|
)
|
||||||
|
BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN"
|
||||||
|
BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE"
|
||||||
|
BIND_INCALL_SERVICE = "android.permission.BIND_INCALL_SERVICE"
|
||||||
|
BIND_INPUT_METHOD = (
|
||||||
|
"android.permission.BIND_INPUT_METHOD"
|
||||||
|
)
|
||||||
|
BIND_MIDI_DEVICE_SERVICE = (
|
||||||
|
"android.permission.BIND_MIDI_DEVICE_SERVICE"
|
||||||
|
)
|
||||||
|
BIND_NFC_SERVICE = (
|
||||||
|
"android.permission.BIND_NFC_SERVICE"
|
||||||
|
)
|
||||||
|
BIND_NOTIFICATION_LISTENER_SERVICE = (
|
||||||
|
"android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
|
||||||
|
)
|
||||||
|
BIND_PRINT_SERVICE = (
|
||||||
|
"android.permission.BIND_PRINT_SERVICE"
|
||||||
|
)
|
||||||
|
BIND_QUICK_SETTINGS_TILE = (
|
||||||
|
"android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||||
|
)
|
||||||
|
BIND_REMOTEVIEWS = (
|
||||||
|
"android.permission.BIND_REMOTEVIEWS"
|
||||||
|
)
|
||||||
|
BIND_SCREENING_SERVICE = (
|
||||||
|
"android.permission.BIND_SCREENING_SERVICE"
|
||||||
|
)
|
||||||
|
BIND_TELECOM_CONNECTION_SERVICE = (
|
||||||
|
"android.permission.BIND_TELECOM_CONNECTION_SERVICE"
|
||||||
|
)
|
||||||
|
BIND_TEXT_SERVICE = (
|
||||||
|
"android.permission.BIND_TEXT_SERVICE"
|
||||||
|
)
|
||||||
|
BIND_TV_INPUT = (
|
||||||
|
"android.permission.BIND_TV_INPUT"
|
||||||
|
)
|
||||||
|
BIND_VISUAL_VOICEMAIL_SERVICE = (
|
||||||
|
"android.permission.BIND_VISUAL_VOICEMAIL_SERVICE"
|
||||||
|
)
|
||||||
|
BIND_VOICE_INTERACTION = (
|
||||||
|
"android.permission.BIND_VOICE_INTERACTION"
|
||||||
|
)
|
||||||
|
BIND_VPN_SERVICE = (
|
||||||
|
"android.permission.BIND_VPN_SERVICE"
|
||||||
|
)
|
||||||
|
BIND_VR_LISTENER_SERVICE = (
|
||||||
|
"android.permission.BIND_VR_LISTENER_SERVICE"
|
||||||
|
)
|
||||||
|
BIND_WALLPAPER = (
|
||||||
|
"android.permission.BIND_WALLPAPER"
|
||||||
|
)
|
||||||
|
BLUETOOTH = (
|
||||||
|
"android.permission.BLUETOOTH"
|
||||||
|
)
|
||||||
|
BLUETOOTH_ADMIN = (
|
||||||
|
"android.permission.BLUETOOTH_ADMIN"
|
||||||
|
)
|
||||||
|
BODY_SENSORS = (
|
||||||
|
"android.permission.BODY_SENSORS"
|
||||||
|
)
|
||||||
|
BROADCAST_PACKAGE_REMOVED = (
|
||||||
|
"android.permission.BROADCAST_PACKAGE_REMOVED"
|
||||||
|
)
|
||||||
|
BROADCAST_STICKY = (
|
||||||
|
"android.permission.BROADCAST_STICKY"
|
||||||
|
)
|
||||||
|
CALL_PHONE = (
|
||||||
|
"android.permission.CALL_PHONE"
|
||||||
|
)
|
||||||
|
CALL_PRIVILEGED = (
|
||||||
|
"android.permission.CALL_PRIVILEGED"
|
||||||
|
)
|
||||||
|
CAMERA = (
|
||||||
|
"android.permission.CAMERA"
|
||||||
|
)
|
||||||
|
CAPTURE_AUDIO_OUTPUT = (
|
||||||
|
"android.permission.CAPTURE_AUDIO_OUTPUT"
|
||||||
|
)
|
||||||
|
CAPTURE_SECURE_VIDEO_OUTPUT = (
|
||||||
|
"android.permission.CAPTURE_SECURE_VIDEO_OUTPUT"
|
||||||
|
)
|
||||||
|
CAPTURE_VIDEO_OUTPUT = (
|
||||||
|
"android.permission.CAPTURE_VIDEO_OUTPUT"
|
||||||
|
)
|
||||||
|
CHANGE_COMPONENT_ENABLED_STATE = (
|
||||||
|
"android.permission.CHANGE_COMPONENT_ENABLED_STATE"
|
||||||
|
)
|
||||||
|
CHANGE_CONFIGURATION = (
|
||||||
|
"android.permission.CHANGE_CONFIGURATION"
|
||||||
|
)
|
||||||
|
CHANGE_NETWORK_STATE = (
|
||||||
|
"android.permission.CHANGE_NETWORK_STATE"
|
||||||
|
)
|
||||||
|
CHANGE_WIFI_MULTICAST_STATE = (
|
||||||
|
"android.permission.CHANGE_WIFI_MULTICAST_STATE"
|
||||||
|
)
|
||||||
|
CHANGE_WIFI_STATE = (
|
||||||
|
"android.permission.CHANGE_WIFI_STATE"
|
||||||
|
)
|
||||||
|
CLEAR_APP_CACHE = (
|
||||||
|
"android.permission.CLEAR_APP_CACHE"
|
||||||
|
)
|
||||||
|
CONTROL_LOCATION_UPDATES = (
|
||||||
|
"android.permission.CONTROL_LOCATION_UPDATES"
|
||||||
|
)
|
||||||
|
DELETE_CACHE_FILES = (
|
||||||
|
"android.permission.DELETE_CACHE_FILES"
|
||||||
|
)
|
||||||
|
DELETE_PACKAGES = (
|
||||||
|
"android.permission.DELETE_PACKAGES"
|
||||||
|
)
|
||||||
|
DIAGNOSTIC = (
|
||||||
|
"android.permission.DIAGNOSTIC"
|
||||||
|
)
|
||||||
|
DISABLE_KEYGUARD = (
|
||||||
|
"android.permission.DISABLE_KEYGUARD"
|
||||||
|
)
|
||||||
|
DUMP = (
|
||||||
|
"android.permission.DUMP"
|
||||||
|
)
|
||||||
|
EXPAND_STATUS_BAR = (
|
||||||
|
"android.permission.EXPAND_STATUS_BAR"
|
||||||
|
)
|
||||||
|
FACTORY_TEST = (
|
||||||
|
"android.permission.FACTORY_TEST"
|
||||||
|
)
|
||||||
|
FOREGROUND_SERVICE = (
|
||||||
|
"android.permission.FOREGROUND_SERVICE"
|
||||||
|
)
|
||||||
|
GET_ACCOUNTS = (
|
||||||
|
"android.permission.GET_ACCOUNTS"
|
||||||
|
)
|
||||||
|
GET_ACCOUNTS_PRIVILEGED = (
|
||||||
|
"android.permission.GET_ACCOUNTS_PRIVILEGED"
|
||||||
|
)
|
||||||
|
GET_PACKAGE_SIZE = (
|
||||||
|
"android.permission.GET_PACKAGE_SIZE"
|
||||||
|
)
|
||||||
|
GET_TASKS = (
|
||||||
|
"android.permission.GET_TASKS"
|
||||||
|
)
|
||||||
|
GLOBAL_SEARCH = (
|
||||||
|
"android.permission.GLOBAL_SEARCH"
|
||||||
|
)
|
||||||
|
INSTALL_LOCATION_PROVIDER = (
|
||||||
|
"android.permission.INSTALL_LOCATION_PROVIDER"
|
||||||
|
)
|
||||||
|
INSTALL_PACKAGES = (
|
||||||
|
"android.permission.INSTALL_PACKAGES"
|
||||||
|
)
|
||||||
|
INSTALL_SHORTCUT = (
|
||||||
|
"com.android.launcher.permission.INSTALL_SHORTCUT"
|
||||||
|
)
|
||||||
|
INSTANT_APP_FOREGROUND_SERVICE = (
|
||||||
|
"android.permission.INSTANT_APP_FOREGROUND_SERVICE"
|
||||||
|
)
|
||||||
|
INTERNET = (
|
||||||
|
"android.permission.INTERNET"
|
||||||
|
)
|
||||||
|
KILL_BACKGROUND_PROCESSES = (
|
||||||
|
"android.permission.KILL_BACKGROUND_PROCESSES"
|
||||||
|
)
|
||||||
|
LOCATION_HARDWARE = (
|
||||||
|
"android.permission.LOCATION_HARDWARE"
|
||||||
|
)
|
||||||
|
MANAGE_DOCUMENTS = (
|
||||||
|
"android.permission.MANAGE_DOCUMENTS"
|
||||||
|
)
|
||||||
|
MANAGE_OWN_CALLS = (
|
||||||
|
"android.permission.MANAGE_OWN_CALLS"
|
||||||
|
)
|
||||||
|
MASTER_CLEAR = (
|
||||||
|
"android.permission.MASTER_CLEAR"
|
||||||
|
)
|
||||||
|
MEDIA_CONTENT_CONTROL = (
|
||||||
|
"android.permission.MEDIA_CONTENT_CONTROL"
|
||||||
|
)
|
||||||
|
MODIFY_AUDIO_SETTINGS = (
|
||||||
|
"android.permission.MODIFY_AUDIO_SETTINGS"
|
||||||
|
)
|
||||||
|
MODIFY_PHONE_STATE = (
|
||||||
|
"android.permission.MODIFY_PHONE_STATE"
|
||||||
|
)
|
||||||
|
MOUNT_FORMAT_FILESYSTEMS = (
|
||||||
|
"android.permission.MOUNT_FORMAT_FILESYSTEMS"
|
||||||
|
)
|
||||||
|
MOUNT_UNMOUNT_FILESYSTEMS = (
|
||||||
|
"android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
|
||||||
|
)
|
||||||
|
NFC = (
|
||||||
|
"android.permission.NFC"
|
||||||
|
)
|
||||||
|
NFC_TRANSACTION_EVENT = (
|
||||||
|
"android.permission.NFC_TRANSACTION_EVENT"
|
||||||
|
)
|
||||||
|
PACKAGE_USAGE_STATS = (
|
||||||
|
"android.permission.PACKAGE_USAGE_STATS"
|
||||||
|
)
|
||||||
|
PERSISTENT_ACTIVITY = (
|
||||||
|
"android.permission.PERSISTENT_ACTIVITY"
|
||||||
|
)
|
||||||
|
PROCESS_OUTGOING_CALLS = (
|
||||||
|
"android.permission.PROCESS_OUTGOING_CALLS"
|
||||||
|
)
|
||||||
|
READ_CALENDAR = (
|
||||||
|
"android.permission.READ_CALENDAR"
|
||||||
|
)
|
||||||
|
READ_CALL_LOG = (
|
||||||
|
"android.permission.READ_CALL_LOG"
|
||||||
|
)
|
||||||
|
READ_CONTACTS = (
|
||||||
|
"android.permission.READ_CONTACTS"
|
||||||
|
)
|
||||||
|
READ_EXTERNAL_STORAGE = (
|
||||||
|
"android.permission.READ_EXTERNAL_STORAGE"
|
||||||
|
)
|
||||||
|
READ_FRAME_BUFFER = (
|
||||||
|
"android.permission.READ_FRAME_BUFFER"
|
||||||
|
)
|
||||||
|
READ_INPUT_STATE = (
|
||||||
|
"android.permission.READ_INPUT_STATE"
|
||||||
|
)
|
||||||
|
READ_LOGS = (
|
||||||
|
"android.permission.READ_LOGS"
|
||||||
|
)
|
||||||
|
READ_PHONE_NUMBERS = (
|
||||||
|
"android.permission.READ_PHONE_NUMBERS"
|
||||||
|
)
|
||||||
|
READ_PHONE_STATE = (
|
||||||
|
"android.permission.READ_PHONE_STATE"
|
||||||
|
)
|
||||||
|
READ_SMS = (
|
||||||
|
"android.permission.READ_SMS"
|
||||||
|
)
|
||||||
|
READ_SYNC_SETTINGS = (
|
||||||
|
"android.permission.READ_SYNC_SETTINGS"
|
||||||
|
)
|
||||||
|
READ_SYNC_STATS = (
|
||||||
|
"android.permission.READ_SYNC_STATS"
|
||||||
|
)
|
||||||
|
READ_VOICEMAIL = (
|
||||||
|
"com.android.voicemail.permission.READ_VOICEMAIL"
|
||||||
|
)
|
||||||
|
REBOOT = (
|
||||||
|
"android.permission.REBOOT"
|
||||||
|
)
|
||||||
|
RECEIVE_BOOT_COMPLETED = (
|
||||||
|
"android.permission.RECEIVE_BOOT_COMPLETED"
|
||||||
|
)
|
||||||
|
RECEIVE_MMS = (
|
||||||
|
"android.permission.RECEIVE_MMS"
|
||||||
|
)
|
||||||
|
RECEIVE_SMS = (
|
||||||
|
"android.permission.RECEIVE_SMS"
|
||||||
|
)
|
||||||
|
RECEIVE_WAP_PUSH = (
|
||||||
|
"android.permission.RECEIVE_WAP_PUSH"
|
||||||
|
)
|
||||||
|
RECORD_AUDIO = (
|
||||||
|
"android.permission.RECORD_AUDIO"
|
||||||
|
)
|
||||||
|
REORDER_TASKS = (
|
||||||
|
"android.permission.REORDER_TASKS"
|
||||||
|
)
|
||||||
|
REQUEST_COMPANION_RUN_IN_BACKGROUND = (
|
||||||
|
"android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND"
|
||||||
|
)
|
||||||
|
REQUEST_COMPANION_USE_DATA_IN_BACKGROUND = (
|
||||||
|
"android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND"
|
||||||
|
)
|
||||||
|
REQUEST_DELETE_PACKAGES = (
|
||||||
|
"android.permission.REQUEST_DELETE_PACKAGES"
|
||||||
|
)
|
||||||
|
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = (
|
||||||
|
"android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"
|
||||||
|
)
|
||||||
|
REQUEST_INSTALL_PACKAGES = (
|
||||||
|
"android.permission.REQUEST_INSTALL_PACKAGES"
|
||||||
|
)
|
||||||
|
RESTART_PACKAGES = (
|
||||||
|
"android.permission.RESTART_PACKAGES"
|
||||||
|
)
|
||||||
|
SEND_RESPOND_VIA_MESSAGE = (
|
||||||
|
"android.permission.SEND_RESPOND_VIA_MESSAGE"
|
||||||
|
)
|
||||||
|
SEND_SMS = (
|
||||||
|
"android.permission.SEND_SMS"
|
||||||
|
)
|
||||||
|
SET_ALARM = (
|
||||||
|
"com.android.alarm.permission.SET_ALARM"
|
||||||
|
)
|
||||||
|
SET_ALWAYS_FINISH = (
|
||||||
|
"android.permission.SET_ALWAYS_FINISH"
|
||||||
|
)
|
||||||
|
SET_ANIMATION_SCALE = (
|
||||||
|
"android.permission.SET_ANIMATION_SCALE"
|
||||||
|
)
|
||||||
|
SET_DEBUG_APP = (
|
||||||
|
"android.permission.SET_DEBUG_APP"
|
||||||
|
)
|
||||||
|
SET_PREFERRED_APPLICATIONS = (
|
||||||
|
"android.permission.SET_PREFERRED_APPLICATIONS"
|
||||||
|
)
|
||||||
|
SET_PROCESS_LIMIT = (
|
||||||
|
"android.permission.SET_PROCESS_LIMIT"
|
||||||
|
)
|
||||||
|
SET_TIME = (
|
||||||
|
"android.permission.SET_TIME"
|
||||||
|
)
|
||||||
|
SET_TIME_ZONE = (
|
||||||
|
"android.permission.SET_TIME_ZONE"
|
||||||
|
)
|
||||||
|
SET_WALLPAPER = (
|
||||||
|
"android.permission.SET_WALLPAPER"
|
||||||
|
)
|
||||||
|
SET_WALLPAPER_HINTS = (
|
||||||
|
"android.permission.SET_WALLPAPER_HINTS"
|
||||||
|
)
|
||||||
|
SIGNAL_PERSISTENT_PROCESSES = (
|
||||||
|
"android.permission.SIGNAL_PERSISTENT_PROCESSES"
|
||||||
|
)
|
||||||
|
STATUS_BAR = (
|
||||||
|
"android.permission.STATUS_BAR"
|
||||||
|
)
|
||||||
|
SYSTEM_ALERT_WINDOW = (
|
||||||
|
"android.permission.SYSTEM_ALERT_WINDOW"
|
||||||
|
)
|
||||||
|
TRANSMIT_IR = (
|
||||||
|
"android.permission.TRANSMIT_IR"
|
||||||
|
)
|
||||||
|
UNINSTALL_SHORTCUT = (
|
||||||
|
"com.android.launcher.permission.UNINSTALL_SHORTCUT"
|
||||||
|
)
|
||||||
|
UPDATE_DEVICE_STATS = (
|
||||||
|
"android.permission.UPDATE_DEVICE_STATS"
|
||||||
|
)
|
||||||
|
USE_BIOMETRIC = (
|
||||||
|
"android.permission.USE_BIOMETRIC"
|
||||||
|
)
|
||||||
|
USE_FINGERPRINT = (
|
||||||
|
"android.permission.USE_FINGERPRINT"
|
||||||
|
)
|
||||||
|
USE_SIP = (
|
||||||
|
"android.permission.USE_SIP"
|
||||||
|
)
|
||||||
|
VIBRATE = (
|
||||||
|
"android.permission.VIBRATE"
|
||||||
|
)
|
||||||
|
WAKE_LOCK = (
|
||||||
|
"android.permission.WAKE_LOCK"
|
||||||
|
)
|
||||||
|
WRITE_APN_SETTINGS = (
|
||||||
|
"android.permission.WRITE_APN_SETTINGS"
|
||||||
|
)
|
||||||
|
WRITE_CALENDAR = (
|
||||||
|
"android.permission.WRITE_CALENDAR"
|
||||||
|
)
|
||||||
|
WRITE_CALL_LOG = (
|
||||||
|
"android.permission.WRITE_CALL_LOG"
|
||||||
|
)
|
||||||
|
WRITE_CONTACTS = (
|
||||||
|
"android.permission.WRITE_CONTACTS"
|
||||||
|
)
|
||||||
|
WRITE_EXTERNAL_STORAGE = (
|
||||||
|
"android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
)
|
||||||
|
WRITE_GSERVICES = (
|
||||||
|
"android.permission.WRITE_GSERVICES"
|
||||||
|
)
|
||||||
|
WRITE_SECURE_SETTINGS = (
|
||||||
|
"android.permission.WRITE_SECURE_SETTINGS"
|
||||||
|
)
|
||||||
|
WRITE_SETTINGS = (
|
||||||
|
"android.permission.WRITE_SETTINGS"
|
||||||
|
)
|
||||||
|
WRITE_SYNC_SETTINGS = (
|
||||||
|
"android.permission.WRITE_SYNC_SETTINGS"
|
||||||
|
)
|
||||||
|
WRITE_VOICEMAIL = (
|
||||||
|
"com.android.voicemail.permission.WRITE_VOICEMAIL"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def request_permissions(permissions):
|
||||||
|
python_activity = autoclass('org.kivy.android.PythonActivity')
|
||||||
|
python_activity.requestPermissions(permissions)
|
||||||
|
|
||||||
|
|
||||||
|
def request_permission(permission):
|
||||||
|
request_permissions([permission])
|
||||||
|
|
||||||
|
|
||||||
|
def check_permission(permission):
|
||||||
|
python_activity = autoclass('org.kivy.android.PythonActivity')
|
||||||
|
result = bool(python_activity.checkCurrentPermission(
|
||||||
|
permission + ""
|
||||||
|
))
|
||||||
|
return result
|
|
@ -33,12 +33,13 @@ class Runnable(PythonJavaClass):
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
self.func(*self.args, **self.kwargs)
|
self.func(*self.args, **self.kwargs)
|
||||||
except:
|
except: # noqa E722
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
Runnable.__runnables__.remove(self)
|
Runnable.__runnables__.remove(self)
|
||||||
|
|
||||||
|
|
||||||
def run_on_ui_thread(f):
|
def run_on_ui_thread(f):
|
||||||
'''Decorator to create automatically a :class:`Runnable` object with the
|
'''Decorator to create automatically a :class:`Runnable` object with the
|
||||||
function. The function will be delayed and call into the Activity thread.
|
function. The function will be delayed and call into the Activity thread.
|
||||||
|
|
|
@ -6,7 +6,7 @@ lib_dict = {
|
||||||
'pygame': ['sdl'],
|
'pygame': ['sdl'],
|
||||||
'sdl2': ['SDL2', 'SDL2_image', 'SDL2_mixer', 'SDL2_ttf']
|
'sdl2': ['SDL2', 'SDL2_image', 'SDL2_mixer', 'SDL2_ttf']
|
||||||
}
|
}
|
||||||
sdl_libs = lib_dict[os.environ['BOOTSTRAP']] if os.environ['BOOTSTRAP'] == 'sdl2' else []
|
sdl_libs = lib_dict.get(os.environ['BOOTSTRAP'], [])
|
||||||
|
|
||||||
renpy_sound = Extension('android._android_sound',
|
renpy_sound = Extension('android._android_sound',
|
||||||
['android/_android_sound.c', 'android/_android_sound_jni.c', ],
|
['android/_android_sound.c', 'android/_android_sound_jni.c', ],
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
from pythonforandroid.toolchain import PythonRecipe, shprint, shutil, current_directory
|
from pythonforandroid.recipe import PythonRecipe
|
||||||
from os.path import join, exists
|
from pythonforandroid.toolchain import current_directory, shprint
|
||||||
import sh
|
import sh
|
||||||
|
|
||||||
|
|
||||||
class ApswRecipe(PythonRecipe):
|
class ApswRecipe(PythonRecipe):
|
||||||
version = '3.15.0-r1'
|
version = '3.15.0-r1'
|
||||||
url = 'https://github.com/rogerbinns/apsw/archive/{version}.tar.gz'
|
url = 'https://github.com/rogerbinns/apsw/archive/{version}.tar.gz'
|
||||||
depends = ['sqlite3', 'hostpython2', 'python2', 'setuptools']
|
depends = ['sqlite3', ('python2', 'python3'), 'setuptools']
|
||||||
call_hostpython_via_targetpython = False
|
call_hostpython_via_targetpython = False
|
||||||
site_packages_name = 'apsw'
|
site_packages_name = 'apsw'
|
||||||
|
|
||||||
|
@ -17,21 +18,17 @@ class ApswRecipe(PythonRecipe):
|
||||||
shprint(hostpython,
|
shprint(hostpython,
|
||||||
'setup.py',
|
'setup.py',
|
||||||
'build_ext',
|
'build_ext',
|
||||||
'--enable=fts4'
|
'--enable=fts4', _env=env)
|
||||||
, _env=env)
|
|
||||||
# Install python bindings
|
# Install python bindings
|
||||||
super(ApswRecipe, self).build_arch(arch)
|
super(ApswRecipe, self).build_arch(arch)
|
||||||
|
|
||||||
def get_recipe_env(self, arch):
|
def get_recipe_env(self, arch):
|
||||||
env = super(ApswRecipe, self).get_recipe_env(arch)
|
env = super(ApswRecipe, self).get_recipe_env(arch)
|
||||||
env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
|
sqlite_recipe = self.get_recipe('sqlite3', self.ctx)
|
||||||
env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python2.7' + \
|
env['CFLAGS'] += ' -I' + sqlite_recipe.get_build_dir(arch.arch)
|
||||||
' -I' + self.get_recipe('sqlite3', self.ctx).get_build_dir(arch.arch)
|
env['LDFLAGS'] += ' -L' + sqlite_recipe.get_lib_dir(arch)
|
||||||
# Set linker to use the correct gcc
|
env['LIBS'] = env.get('LIBS', '') + ' -lsqlite3'
|
||||||
env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
|
|
||||||
env['LDFLAGS'] += ' -L' + env['PYTHON_ROOT'] + '/lib' + \
|
|
||||||
' -lpython2.7' + \
|
|
||||||
' -lsqlite3'
|
|
||||||
return env
|
return env
|
||||||
|
|
||||||
|
|
||||||
recipe = ApswRecipe()
|
recipe = ApswRecipe()
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe
|
from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe
|
||||||
|
|
||||||
|
|
||||||
class AtomRecipe(CppCompiledComponentsPythonRecipe):
|
class AtomRecipe(CppCompiledComponentsPythonRecipe):
|
||||||
site_packages_name = 'atom'
|
site_packages_name = 'atom'
|
||||||
version = '0.3.10'
|
version = '0.3.10'
|
||||||
url = 'https://github.com/nucleic/atom/archive/master.zip'
|
url = 'https://github.com/nucleic/atom/archive/master.zip'
|
||||||
depends = ['python2','setuptools']
|
depends = ['setuptools']
|
||||||
|
|
||||||
|
|
||||||
recipe = AtomRecipe()
|
recipe = AtomRecipe()
|
||||||
|
|
|
@ -1,36 +1,32 @@
|
||||||
|
|
||||||
from pythonforandroid.toolchain import CythonRecipe, shprint, current_directory, info
|
from pythonforandroid.recipe import CythonRecipe
|
||||||
import sh
|
from os.path import join
|
||||||
import glob
|
|
||||||
from os.path import join, exists
|
|
||||||
|
|
||||||
|
|
||||||
class AudiostreamRecipe(CythonRecipe):
|
class AudiostreamRecipe(CythonRecipe):
|
||||||
version = 'master'
|
version = 'master'
|
||||||
url = 'https://github.com/kivy/audiostream/archive/{version}.zip'
|
url = 'https://github.com/kivy/audiostream/archive/{version}.zip'
|
||||||
name = 'audiostream'
|
name = 'audiostream'
|
||||||
depends = ['python2', ('sdl', 'sdl2'), 'pyjnius']
|
depends = [('python2', 'python3'), ('sdl', 'sdl2'), 'pyjnius']
|
||||||
|
|
||||||
def get_recipe_env(self, arch):
|
def get_recipe_env(self, arch):
|
||||||
|
env = super(AudiostreamRecipe, self).get_recipe_env(arch)
|
||||||
if 'sdl' in self.ctx.recipe_build_order:
|
if 'sdl' in self.ctx.recipe_build_order:
|
||||||
sdl_include = 'sdl'
|
sdl_include = 'sdl'
|
||||||
sdl_mixer_include = 'sdl_mixer'
|
sdl_mixer_include = 'sdl_mixer'
|
||||||
elif 'sdl2' in self.ctx.recipe_build_order:
|
elif 'sdl2' in self.ctx.recipe_build_order:
|
||||||
sdl_include = 'SDL'
|
sdl_include = 'SDL2'
|
||||||
sdl_mixer_include = 'SDL2_mixer'
|
sdl_mixer_include = 'SDL2_mixer'
|
||||||
|
env['USE_SDL2'] = 'True'
|
||||||
|
env['SDL2_INCLUDE_DIR'] = join(self.ctx.bootstrap.build_dir, 'jni', 'SDL', 'include')
|
||||||
|
|
||||||
#note: audiostream library is not yet able to judge whether it is being used with sdl or with sdl2.
|
|
||||||
#this causes linking to fail against SDL2 (compiling against SDL2 works)
|
|
||||||
#need to find a way to fix this in audiostream's setup.py
|
|
||||||
raise RuntimeError('Audiostream library is not yet able to configure itself to link against SDL2. Patch on audiostream library needed - any help much appreciated!')
|
|
||||||
|
|
||||||
env = super(AudiostreamRecipe, self).get_recipe_env(arch)
|
|
||||||
env['CFLAGS'] += ' -I{jni_path}/{sdl_include}/include -I{jni_path}/{sdl_mixer_include}'.format(
|
env['CFLAGS'] += ' -I{jni_path}/{sdl_include}/include -I{jni_path}/{sdl_mixer_include}'.format(
|
||||||
jni_path=join(self.ctx.bootstrap.build_dir, 'jni'),
|
jni_path=join(self.ctx.bootstrap.build_dir, 'jni'),
|
||||||
sdl_include=sdl_include,
|
sdl_include=sdl_include,
|
||||||
sdl_mixer_include=sdl_mixer_include)
|
sdl_mixer_include=sdl_mixer_include)
|
||||||
|
env['NDKPLATFORM'] = self.ctx.ndk_platform
|
||||||
|
env['LIBLINK'] = 'NOTNONE' # Hacky fix. Needed by audiostream setup.py
|
||||||
return env
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
recipe = AudiostreamRecipe()
|
recipe = AudiostreamRecipe()
|
||||||
|
|
|
@ -3,10 +3,10 @@ from pythonforandroid.recipe import PythonRecipe
|
||||||
|
|
||||||
class BabelRecipe(PythonRecipe):
|
class BabelRecipe(PythonRecipe):
|
||||||
name = 'babel'
|
name = 'babel'
|
||||||
version = '2.1.1'
|
version = '2.2.0'
|
||||||
url = 'https://pypi.python.org/packages/source/B/Babel/Babel-{version}.tar.gz'
|
url = 'https://pypi.python.org/packages/source/B/Babel/Babel-{version}.tar.gz'
|
||||||
|
|
||||||
depends = [('python2', 'python3crystax'), 'setuptools', 'pytz']
|
depends = ['setuptools', 'pytz']
|
||||||
|
|
||||||
call_hostpython_via_targetpython = False
|
call_hostpython_via_targetpython = False
|
||||||
install_in_hostpython = True
|
install_in_hostpython = True
|
||||||
|
|
|
@ -1,17 +1,45 @@
|
||||||
from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory
|
from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory
|
||||||
from os.path import join, exists
|
from os.path import join, exists
|
||||||
|
from os import environ
|
||||||
import sh
|
import sh
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This recipe creates a custom toolchain and bootstraps Boost from source to build Boost.Build
|
This recipe creates a custom toolchain and bootstraps Boost from source to build Boost.Build
|
||||||
including python bindings
|
including python bindings
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class BoostRecipe(Recipe):
|
class BoostRecipe(Recipe):
|
||||||
version = '1.60.0'
|
# Todo: make recipe compatible with all p4a architectures
|
||||||
# Don't forget to change the URL when changing the version
|
'''
|
||||||
url = 'http://downloads.sourceforge.net/project/boost/boost/{version}/boost_1_60_0.tar.bz2'
|
.. note:: This recipe can be built only against API 21+ and arch armeabi-v7a
|
||||||
depends = ['python2']
|
|
||||||
patches = ['disable-so-version.patch', 'use-android-libs.patch']
|
.. versionchanged:: 0.6.0
|
||||||
|
Rewrote recipe to support clang's build. The following changes has
|
||||||
|
been made:
|
||||||
|
|
||||||
|
- Bumped version number to 1.68.0
|
||||||
|
- Better version handling for url
|
||||||
|
- Added python 3 compatibility
|
||||||
|
- Default compiler for ndk's toolchain set to clang
|
||||||
|
- Python version will be detected via user-config.jam
|
||||||
|
- Changed stl's lib from ``gnustl_shared`` to ``c++_shared``
|
||||||
|
'''
|
||||||
|
version = '1.68.0'
|
||||||
|
url = 'http://downloads.sourceforge.net/project/boost/' \
|
||||||
|
'boost/{version}/boost_{version_underscore}.tar.bz2'
|
||||||
|
depends = [('python2', 'python3')]
|
||||||
|
patches = ['disable-so-version.patch',
|
||||||
|
'use-android-libs.patch',
|
||||||
|
'fix-android-issues.patch']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def versioned_url(self):
|
||||||
|
if self.url is None:
|
||||||
|
return None
|
||||||
|
return self.url.format(
|
||||||
|
version=self.version,
|
||||||
|
version_underscore=self.version.replace('.', '_'))
|
||||||
|
|
||||||
def should_build(self, arch):
|
def should_build(self, arch):
|
||||||
return not exists(join(self.get_build_dir(arch.arch), 'b2'))
|
return not exists(join(self.get_build_dir(arch.arch), 'b2'))
|
||||||
|
@ -26,7 +54,9 @@ class BoostRecipe(Recipe):
|
||||||
shprint(bash, join(self.ctx.ndk_dir, 'build/tools/make-standalone-toolchain.sh'),
|
shprint(bash, join(self.ctx.ndk_dir, 'build/tools/make-standalone-toolchain.sh'),
|
||||||
'--arch=' + env['ARCH'],
|
'--arch=' + env['ARCH'],
|
||||||
'--platform=android-' + str(self.ctx.android_api),
|
'--platform=android-' + str(self.ctx.android_api),
|
||||||
'--toolchain=' + env['CROSSHOST'] + '-' + env['TOOLCHAIN_VERSION'],
|
'--toolchain=' + env['CROSSHOST'] + '-' + self.ctx.toolchain_version + ':-llvm',
|
||||||
|
'--use-llvm',
|
||||||
|
'--stl=libc++',
|
||||||
'--install-dir=' + env['CROSSHOME']
|
'--install-dir=' + env['CROSSHOME']
|
||||||
)
|
)
|
||||||
# Set custom configuration
|
# Set custom configuration
|
||||||
|
@ -36,31 +66,38 @@ class BoostRecipe(Recipe):
|
||||||
def build_arch(self, arch):
|
def build_arch(self, arch):
|
||||||
super(BoostRecipe, self).build_arch(arch)
|
super(BoostRecipe, self).build_arch(arch)
|
||||||
env = self.get_recipe_env(arch)
|
env = self.get_recipe_env(arch)
|
||||||
|
env['PYTHON_HOST'] = self.ctx.hostpython
|
||||||
with current_directory(self.get_build_dir(arch.arch)):
|
with current_directory(self.get_build_dir(arch.arch)):
|
||||||
# Compile Boost.Build engine with this custom toolchain
|
# Compile Boost.Build engine with this custom toolchain
|
||||||
bash = sh.Command('bash')
|
bash = sh.Command('bash')
|
||||||
shprint(bash, 'bootstrap.sh',
|
shprint(bash, 'bootstrap.sh') # Do not pass env
|
||||||
'--with-python=' + join(env['PYTHON_ROOT'], 'bin/python.host'),
|
|
||||||
'--with-python-version=2.7',
|
|
||||||
'--with-python-root=' + env['PYTHON_ROOT']
|
|
||||||
) # Do not pass env
|
|
||||||
# Install app stl
|
# Install app stl
|
||||||
shutil.copyfile(join(env['CROSSHOME'], env['CROSSHOST'], 'lib/libgnustl_shared.so'),
|
shutil.copyfile(
|
||||||
join(self.ctx.get_libs_dir(arch.arch), 'libgnustl_shared.so'))
|
join(self.ctx.ndk_dir, 'sources/cxx-stl/llvm-libc++/libs/'
|
||||||
|
'armeabi-v7a/libc++_shared.so'),
|
||||||
|
join(self.ctx.get_libs_dir(arch.arch), 'libc++_shared.so'))
|
||||||
|
|
||||||
def select_build_arch(self, arch):
|
def select_build_arch(self, arch):
|
||||||
return arch.arch.replace('eabi-v7a', '').replace('eabi', '')
|
return arch.arch.replace('eabi-v7a', '').replace('eabi', '')
|
||||||
|
|
||||||
def get_recipe_env(self, arch):
|
def get_recipe_env(self, arch):
|
||||||
env = super(BoostRecipe, self).get_recipe_env(arch)
|
# We don't use the normal env because we
|
||||||
|
# are building with a standalone toolchain
|
||||||
|
env = environ.copy()
|
||||||
|
|
||||||
env['BOOST_BUILD_PATH'] = self.get_build_dir(arch.arch) # find user-config.jam
|
env['BOOST_BUILD_PATH'] = self.get_build_dir(arch.arch) # find user-config.jam
|
||||||
env['BOOST_ROOT'] = env['BOOST_BUILD_PATH'] # find boost source
|
env['BOOST_ROOT'] = env['BOOST_BUILD_PATH'] # find boost source
|
||||||
env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
|
|
||||||
|
env['PYTHON_ROOT'] = self.ctx.python_recipe.link_root(arch.arch)
|
||||||
|
env['PYTHON_INCLUDE'] = self.ctx.python_recipe.include_root(arch.arch)
|
||||||
|
env['PYTHON_MAJOR_MINOR'] = self.ctx.python_recipe.version[:3]
|
||||||
|
env['PYTHON_LINK_VERSION'] = self.ctx.python_recipe.major_minor_version_string
|
||||||
|
if 'python3' in self.ctx.python_recipe.name:
|
||||||
|
env['PYTHON_LINK_VERSION'] += 'm'
|
||||||
|
|
||||||
env['ARCH'] = self.select_build_arch(arch)
|
env['ARCH'] = self.select_build_arch(arch)
|
||||||
env['ANDROIDAPI'] = str(self.ctx.android_api)
|
|
||||||
env['CROSSHOST'] = env['ARCH'] + '-linux-androideabi'
|
env['CROSSHOST'] = env['ARCH'] + '-linux-androideabi'
|
||||||
env['CROSSHOME'] = join(env['BOOST_ROOT'], 'standalone-' + env['ARCH'] + '-toolchain')
|
env['CROSSHOME'] = join(env['BOOST_ROOT'], 'standalone-' + env['ARCH'] + '-toolchain')
|
||||||
env['TOOLCHAIN_PREFIX'] = join(env['CROSSHOME'], 'bin', env['CROSSHOST'])
|
|
||||||
return env
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
|
68
p4a/pythonforandroid/recipes/boost/fix-android-issues.patch
Normal file
68
p4a/pythonforandroid/recipes/boost/fix-android-issues.patch
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
diff -u -r boost_1_68_0.orig/boost/config/user.hpp boost_1_68_0/boost/config/user.hpp
|
||||||
|
--- boost_1_68_0.orig/boost/config/user.hpp 2018-08-01 22:50:46.000000000 +0200
|
||||||
|
+++ boost_1_68_0/boost/config/user.hpp 2018-08-27 15:43:38.000000000 +0200
|
||||||
|
@@ -13,6 +13,12 @@
|
||||||
|
// configuration policy:
|
||||||
|
//
|
||||||
|
|
||||||
|
+// Android defines
|
||||||
|
+// There is problem with std::atomic on android (and some other platforms).
|
||||||
|
+// See this link for more info:
|
||||||
|
+// https://code.google.com/p/android/issues/detail?id=42735#makechanges
|
||||||
|
+#define BOOST_ASIO_DISABLE_STD_ATOMIC 1
|
||||||
|
+
|
||||||
|
// define this to locate a compiler config file:
|
||||||
|
// #define BOOST_COMPILER_CONFIG <myheader>
|
||||||
|
|
||||||
|
diff -u -r boost_1_68_0.orig/boost/asio/detail/config.hpp boost_1_68_0/boost/asio/detail/config.hpp
|
||||||
|
--- boost_1_68_0.orig/boost/asio/detail/config.hpp 2018-08-01 22:50:46.000000000 +0200
|
||||||
|
+++ boost_1_68_0/boost/asio/detail/config.hpp 2018-09-19 12:39:56.000000000 +0200
|
||||||
|
@@ -804,7 +804,11 @@
|
||||||
|
# if defined(__clang__)
|
||||||
|
# if (__cplusplus >= 201402)
|
||||||
|
# if __has_include(<experimental/string_view>)
|
||||||
|
-# define BOOST_ASIO_HAS_STD_EXPERIMENTAL_STRING_VIEW 1
|
||||||
|
+# if __clang_major__ >= 7
|
||||||
|
+# undef BOOST_ASIO_HAS_STD_EXPERIMENTAL_STRING_VIEW
|
||||||
|
+# else
|
||||||
|
+# define BOOST_ASIO_HAS_STD_EXPERIMENTAL_STRING_VIEW 1
|
||||||
|
+# endif // __clang_major__ >= 7
|
||||||
|
# endif // __has_include(<experimental/string_view>)
|
||||||
|
# endif // (__cplusplus >= 201402)
|
||||||
|
# endif // defined(__clang__)
|
||||||
|
diff -u -r boost_1_68_0.orig/boost/system/error_code.hpp boost_1_68_0/boost/system/error_code.hpp
|
||||||
|
--- boost_1_68_0.orig/boost/system/error_code.hpp 2018-08-01 22:50:53.000000000 +0200
|
||||||
|
+++ boost_1_68_0/boost/system/error_code.hpp 2018-08-27 15:44:29.000000000 +0200
|
||||||
|
@@ -17,6 +17,7 @@
|
||||||
|
#include <boost/assert.hpp>
|
||||||
|
#include <boost/noncopyable.hpp>
|
||||||
|
#include <boost/utility/enable_if.hpp>
|
||||||
|
+#include <stdio.h>
|
||||||
|
#include <ostream>
|
||||||
|
#include <string>
|
||||||
|
#include <stdexcept>
|
||||||
|
diff -u -r boost_1_68_0.orig/libs/filesystem/src/operations.cpp boost_1_68_0/libs/filesystem/src/operations.cpp
|
||||||
|
--- boost_1_68_0.orig/libs/filesystem/src/operations.cpp 2018-08-01 22:50:47.000000000 +0200
|
||||||
|
+++ boost_1_68_0/libs/filesystem/src/operations.cpp 2018-08-27 15:47:15.000000000 +0200
|
||||||
|
@@ -232,6 +232,21 @@
|
||||||
|
|
||||||
|
# if defined(BOOST_POSIX_API)
|
||||||
|
|
||||||
|
+# if defined(__ANDROID__)
|
||||||
|
+# define truncate libboost_truncate_wrapper
|
||||||
|
+// truncate() is present in Android libc only starting from ABI 21, so here's a simple wrapper
|
||||||
|
+static int libboost_truncate_wrapper(const char *path, off_t length)
|
||||||
|
+{
|
||||||
|
+ int fd = open(path, O_WRONLY);
|
||||||
|
+ if (fd == -1) {
|
||||||
|
+ return -1;
|
||||||
|
+ }
|
||||||
|
+ int status = ftruncate(fd, length);
|
||||||
|
+ close(fd);
|
||||||
|
+ return status;
|
||||||
|
+}
|
||||||
|
+# endif
|
||||||
|
+
|
||||||
|
typedef int err_t;
|
||||||
|
|
||||||
|
// POSIX uses a 0 return to indicate success
|
|
@ -1,28 +1,61 @@
|
||||||
import os ;
|
import os ;
|
||||||
|
|
||||||
local ANDROIDNDK = [ os.environ ANDROIDNDK ] ;
|
|
||||||
local ANDROIDAPI = [ os.environ ANDROIDAPI ] ;
|
|
||||||
local TOOLCHAIN_VERSION = [ os.environ TOOLCHAIN_VERSION ] ;
|
|
||||||
local TOOLCHAIN_PREFIX = [ os.environ TOOLCHAIN_PREFIX ] ;
|
|
||||||
local ARCH = [ os.environ ARCH ] ;
|
local ARCH = [ os.environ ARCH ] ;
|
||||||
|
local CROSSHOME = [ os.environ CROSSHOME ] ;
|
||||||
|
local PYTHON_HOST = [ os.environ PYTHON_HOST ] ;
|
||||||
local PYTHON_ROOT = [ os.environ PYTHON_ROOT ] ;
|
local PYTHON_ROOT = [ os.environ PYTHON_ROOT ] ;
|
||||||
|
local PYTHON_INCLUDE = [ os.environ PYTHON_INCLUDE ] ;
|
||||||
|
local PYTHON_LINK_VERSION = [ os.environ PYTHON_LINK_VERSION ] ;
|
||||||
|
local PYTHON_MAJOR_MINOR = [ os.environ PYTHON_MAJOR_MINOR ] ;
|
||||||
|
|
||||||
using gcc : $(ARCH) : $(TOOLCHAIN_PREFIX)-g++ :
|
using clang : $(ARCH) : $(CROSSHOME)/bin/arm-linux-androideabi-clang++ :
|
||||||
|
<archiver>$(CROSSHOME)/bin/arm-linux-androideabi-ar
|
||||||
|
<root>$(CROSSHOME)/sysroot
|
||||||
<architecture>$(ARCH)
|
<architecture>$(ARCH)
|
||||||
<archiver>$(TOOLCHAIN_PREFIX)-ar
|
<compileflags>-fexceptions
|
||||||
<compileflags>-DBOOST_SP_USE_PTHREADS
|
<compileflags>-frtti
|
||||||
<compileflags>-DBOOST_AC_USE_PTHREADS
|
<compileflags>-fpic
|
||||||
<cxxflags>-DBOOST_SP_USE_PTHREADS
|
<compileflags>-ffunction-sections
|
||||||
<cxxflags>-DBOOST_AC_USE_PTHREADS
|
<compileflags>-funwind-tables
|
||||||
<cxxflags>-frtti
|
<compileflags>-march=armv7-a
|
||||||
<cxxflags>-fexceptions
|
<compileflags>-msoft-float
|
||||||
<compileflags>-I$(ANDROIDNDK)/platforms/android-$(ANDROIDAPI)/arch-$(ARCH)/usr/include
|
<compileflags>-mfpu=neon
|
||||||
<compileflags>-I$(ANDROIDNDK)/sources/cxx-stl/gnu-libstdc++/$(TOOLCHAIN_VERSION)/include
|
<compileflags>-mthumb
|
||||||
<compileflags>-I$(ANDROIDNDK)/sources/cxx-stl/gnu-libstdc++/$(TOOLCHAIN_VERSION)/libs/$(ARCH)/include
|
<linkflags>-march=armv7-a
|
||||||
<compileflags>-I$(PYTHON_ROOT)/include/python2.7
|
<linkflags>-Wl,--fix-cortex-a8
|
||||||
<linkflags>--sysroot=$(ANDROIDNDK)/platforms/android-$(ANDROIDAPI)/arch-$(ARCH)
|
<compileflags>-Os
|
||||||
<linkflags>-L$(ANDROIDNDK)/sources/cxx-stl/gnu-libstdc++/$(TOOLCHAIN_VERSION)/libs/$(ARCH)
|
<compileflags>-fomit-frame-pointer
|
||||||
<linkflags>-L$(PYTHON_ROOT)/lib
|
<compileflag>-fno-strict-aliasing
|
||||||
<linkflags>-lgnustl_shared
|
<compileflags>-DANDROID
|
||||||
<linkflags>-lpython2.7
|
<compileflags>-D__ANDROID__
|
||||||
|
<compileflags>-DANDROID_TOOLCHAIN=clang
|
||||||
|
<compileflags>-DANDROID_ABI=armv7-a
|
||||||
|
<compileflags>-DANDROID_STL=c++_shared
|
||||||
|
<compileflags>-DBOOST_ALL_NO_LIB
|
||||||
|
#<compileflags>-DNDEBUG
|
||||||
|
<compileflags>-O2
|
||||||
|
<compileflags>-g
|
||||||
|
<compileflags>-fvisibility=hidden
|
||||||
|
<compileflags>-fvisibility-inlines-hidden
|
||||||
|
<compileflags>-fdata-sections
|
||||||
|
<cxxflags>-D__arm__
|
||||||
|
<cxxflags>-D_REENTRANT
|
||||||
|
<cxxflags>-D_GLIBCXX__PTHREADS
|
||||||
|
<compileflags>-Wno-long-long
|
||||||
|
<compileflags>-Wno-missing-field-initializers
|
||||||
|
<compileflags>-Wno-unused-variable
|
||||||
|
<linkflags>-Wl,-z,relro
|
||||||
|
<linkflags>-Wl,-z,now
|
||||||
|
<linkflags>-lc++_shared
|
||||||
|
<linkflags>-L$(PYTHON_ROOT)
|
||||||
|
<linkflags>-lpython$(PYTHON_LINK_VERSION)
|
||||||
|
<linkflags>-Wl,-O1
|
||||||
|
<linkflags>-Wl,-Bsymbolic-functions
|
||||||
|
;
|
||||||
|
|
||||||
|
using python : $(PYTHON_MAJOR_MINOR)
|
||||||
|
: $(PYTHON_host)
|
||||||
|
: $(PYTHON_ROOT) $(PYTHON_INCLUDE)
|
||||||
|
: $(PYTHON_ROOT)/libpython$(PYTHON_LINK_VERSION).so
|
||||||
|
: #<define>BOOST_ALL_DYN_LINK
|
||||||
;
|
;
|
|
@ -1,5 +1,6 @@
|
||||||
from pythonforandroid.toolchain import Recipe
|
from pythonforandroid.toolchain import Recipe
|
||||||
|
|
||||||
|
|
||||||
class BrokenRecipe(Recipe):
|
class BrokenRecipe(Recipe):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
print('This is a broken recipe, not a real one!')
|
print('This is a broken recipe, not a real one!')
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
|
from pythonforandroid.recipe import CompiledComponentsPythonRecipe
|
||||||
from pythonforandroid.toolchain import CompiledComponentsPythonRecipe
|
|
||||||
from pythonforandroid.patching import is_darwin
|
from pythonforandroid.patching import is_darwin
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,7 +7,7 @@ class CdecimalRecipe(CompiledComponentsPythonRecipe):
|
||||||
version = '2.3'
|
version = '2.3'
|
||||||
url = 'http://www.bytereef.org/software/mpdecimal/releases/cdecimal-{version}.tar.gz'
|
url = 'http://www.bytereef.org/software/mpdecimal/releases/cdecimal-{version}.tar.gz'
|
||||||
|
|
||||||
depends = ['python2']
|
depends = []
|
||||||
|
|
||||||
patches = ['locale.patch',
|
patches = ['locale.patch',
|
||||||
'cross-compile.patch']
|
'cross-compile.patch']
|
||||||
|
|
|
@ -1,29 +1,52 @@
|
||||||
|
import os
|
||||||
from pythonforandroid.recipe import CompiledComponentsPythonRecipe
|
from pythonforandroid.recipe import CompiledComponentsPythonRecipe
|
||||||
|
|
||||||
|
|
||||||
class CffiRecipe(CompiledComponentsPythonRecipe):
|
class CffiRecipe(CompiledComponentsPythonRecipe):
|
||||||
|
"""
|
||||||
|
Extra system dependencies: autoconf, automake and libtool.
|
||||||
|
"""
|
||||||
name = 'cffi'
|
name = 'cffi'
|
||||||
version = '1.4.2'
|
version = '1.11.5'
|
||||||
url = 'https://pypi.python.org/packages/source/c/cffi/cffi-{version}.tar.gz'
|
url = 'https://pypi.python.org/packages/source/c/cffi/cffi-{version}.tar.gz'
|
||||||
|
|
||||||
depends = [('python2', 'python3crystax'), 'setuptools', 'pycparser', 'libffi']
|
depends = ['setuptools', 'pycparser', 'libffi']
|
||||||
|
|
||||||
patches = ['disable-pkg-config.patch']
|
patches = ['disable-pkg-config.patch']
|
||||||
|
|
||||||
# call_hostpython_via_targetpython = False
|
# call_hostpython_via_targetpython = False
|
||||||
install_in_hostpython = True
|
install_in_hostpython = True
|
||||||
|
|
||||||
|
def get_hostrecipe_env(self, arch=None):
|
||||||
|
# fixes missing ffi.h on some host systems (e.g. gentoo)
|
||||||
|
env = super(CffiRecipe, self).get_hostrecipe_env(arch)
|
||||||
|
libffi = self.get_recipe('libffi', self.ctx)
|
||||||
|
includes = libffi.get_include_dirs(arch)
|
||||||
|
env['FFI_INC'] = ",".join(includes)
|
||||||
|
return env
|
||||||
|
|
||||||
def get_recipe_env(self, arch=None):
|
def get_recipe_env(self, arch=None):
|
||||||
env = super(CffiRecipe, self).get_recipe_env(arch)
|
env = super(CffiRecipe, self).get_recipe_env(arch)
|
||||||
libffi = self.get_recipe('libffi', self.ctx)
|
libffi = self.get_recipe('libffi', self.ctx)
|
||||||
includes = libffi.get_include_dirs(arch)
|
includes = libffi.get_include_dirs(arch)
|
||||||
env['CFLAGS'] = ' -I'.join([env.get('CFLAGS', '')] + includes)
|
env['CFLAGS'] = ' -I'.join([env.get('CFLAGS', '')] + includes)
|
||||||
|
env['CFLAGS'] += ' -I{}'.format(self.ctx.python_recipe.include_root(arch.arch))
|
||||||
env['LDFLAGS'] = (env.get('CFLAGS', '') + ' -L' +
|
env['LDFLAGS'] = (env.get('CFLAGS', '') + ' -L' +
|
||||||
self.ctx.get_libs_dir(arch.arch))
|
self.ctx.get_libs_dir(arch.arch))
|
||||||
|
env['LDFLAGS'] += ' -L{}'.format(os.path.join(self.ctx.bootstrap.build_dir, 'libs', arch.arch))
|
||||||
|
# required for libc and libdl
|
||||||
|
ndk_dir = self.ctx.ndk_platform
|
||||||
|
ndk_lib_dir = os.path.join(ndk_dir, 'usr', 'lib')
|
||||||
|
env['LDFLAGS'] += ' -L{}'.format(ndk_lib_dir)
|
||||||
|
env['LDFLAGS'] += " --sysroot={}".format(self.ctx.ndk_platform)
|
||||||
env['PYTHONPATH'] = ':'.join([
|
env['PYTHONPATH'] = ':'.join([
|
||||||
self.ctx.get_site_packages_dir(),
|
self.ctx.get_site_packages_dir(),
|
||||||
env['BUILDLIB_PATH'],
|
env['BUILDLIB_PATH'],
|
||||||
])
|
])
|
||||||
|
env['LDFLAGS'] += ' -L{}'.format(self.ctx.python_recipe.link_root(arch.arch))
|
||||||
|
env['LDFLAGS'] += ' -lpython{}'.format(self.ctx.python_recipe.major_minor_version_string)
|
||||||
|
if 'python3' in self.ctx.python_recipe.name:
|
||||||
|
env['LDFLAGS'] += 'm'
|
||||||
return env
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue
Use a switch