在Eclipse / ADT下开发并构建了一个Android应用程序。现在我必须让它以两种形式存在:市场版(带有购买支持的Google库)和客户N的版本(没有Google Play的东西,但有一些自定义功能)。
我无法将其重构为库,因为这会破坏版本控制历史记录。重要的是不要破坏版本控制并确保在公共部分中修复的错误在两种风格中都是固定的。保持目录结构很重要( src / res / libs / )。我不能只引入一个标志,因为要求是不要在市场二进制文件中留下任何自定义功能的痕迹。
如果我想手动删除自定义功能,我会(1)删除一些源文件(包括java和资源)和(2)注释掉一些行在一些其他源文件中(同样,java和XML资源)。
如何使应用程序存在两个版本(风格),功能略有不同?
答案 0 :(得分:0)
所以,没有人发布任何答案。
我希望口味是:
让我们考虑一个具有两种配置的假设项目: master 和 slave 。
这个想法是引入一个标记,一个简单的脚本可以注释掉或取消注释。它更好地显示在图像上:
后两行应理解为"注释掉主配置"。嗯,在实践中,我建议像
这样的东西/*master{*/
if(something) {
...
}
/*}master*/
但很高兴知道你可以把所有东西放在一条线上。 /*}*/
和/*}configName*/
都可以使用。
没有" else":为你不知道的配置指定一些东西是完全错误的。
同样的技巧适用于XML资源:
setconfig.py:
#!/usr/bin/python
# coding: utf-8
import argparse
import os
import re
#set to False for debugging
really = True
verbose = True
# In the file 'fname',
# find the text matching "before oldtext after" (all occurrences) and
# replace 'oldtext' with 'newtext' (all occurrences).
# If 'mandatory' is true, raise an exception if no replacements were made.
def fileReplace(fname,before,newtext,after,oldtextpattern=r"[\w.]+",mandatory=True):
with open(fname, 'r+') as f:
read_data = f.read()
pattern = r"("+re.escape(before)+r")"+oldtextpattern+"("+re.escape(after)+r")"
replacement = r"\g<1>"+newtext+r"\g<2>"
new_data,replacements_made = re.subn(pattern,replacement,read_data,flags=re.MULTILINE)
if replacements_made and really:
if new_data!=read_data:
f.seek(0)
f.truncate()
f.write(new_data)
if verbose:
print "patching ",fname," (",replacements_made," occurrence" + ("s" if 1!=replacements_made else ""),")",newtext,("-- no changes" if new_data==read_data else "-- ***CHANGED***")
elif replacements_made:
print "\n\n\n\n"
print fname,":"
print new_data
elif mandatory:
raise Exception("cannot patch the file: "+fname+" with ["+newtext+"] instead of '"+before+"{"+oldtextpattern+"}"+after+"'")
def enableConfigInFile(flavour,fname, enable=True):
if enable:
symbol=r"*"
oldsymbol=re.escape("/")
else:
symbol=r"/"
oldsymbol=re.escape("*")
fileReplace(fname, "/*"+flavour+"{",symbol,"/",oldtextpattern=oldsymbol, mandatory=False)
def enableConfigInXmlFile(flavour,fname, enable=True):
if enable:
startsymbol=r"<!--"
endsymbol=r"-->"
oldstartsymbol=re.escape("X!==")
oldendsymbol=re.escape("==X")
else:
startsymbol=r"X!=="
endsymbol=r"==X"
oldstartsymbol=re.escape("<!--")
oldendsymbol=re.escape("-->")
fileReplace(fname, "<!--"+flavour+"{", endsymbol, "", oldtextpattern=oldendsymbol, mandatory=False)
fileReplace(fname, "", startsymbol, "}"+flavour+"-->", oldtextpattern=oldstartsymbol, mandatory=False)
def enableConfig(flavour, enable=True):
for root, dirs, files in os.walk(".", topdown=True):
# ignore version control directories
# Why .hg- and .git- ? Sometimes we need to hide .hg or .git from programs like `meld`.
# (`meld` ignores .hg if it finds .git)
if '.svn' in dirs:
dirs.remove('.svn')
if '.svn-' in dirs:
dirs.remove('.svn-')
if '.hg' in dirs:
dirs.remove('.hg')
if '.hg-' in dirs:
dirs.remove('.hg-')
if '.git' in dirs:
dirs.remove('.git')
if '.git-' in dirs:
dirs.remove('.git-')
for name in files:
if name.endswith('.java'):
enableConfigInFile(flavour, os.path.join(root, name), enable)
elif name.endswith('.xml'):
enableConfigInXmlFile(flavour, os.path.join(root, name), enable)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('--master', dest='conf', action='store_const', const='master', help='configure as the master')
parser.add_argument('--slave', dest='conf', action='store_const', const='slave', help='configure as a slave')
args = parser.parse_args()
if args.conf is None:
parser.print_help()
else:
print "setting configuration: "+args.conf
# this is where you can implement a hierarchy of configurations: e.g. super-master would enable both `master` and `super-master`
if args.conf == "master":
enableConfig("slave", enable=False)
enableConfig("master")
elif args.conf == "slave":
enableConfig("master", enable=False)
enableConfig("slave")
# how to extend this to a hierarchy of configurations:
# let the child configuration call enableConfig() first for the parent and then for itself
# elif args.conf == "supermaster":
# enableConfig("slave", enable=False)
# enableConfig("master")
# enableConfig("supermaster")
用作:
./setconfig.py --slave
或
./setconfig.py --master
下行:/** docs */
条评论会干扰/*master{*/
... /*}master*/
结构。
以防万一,我使用的是Python 2.7.3。
欢迎您使用任何其他语言(如perl或bash)重写上述脚本并发布代码。
答案 1 :(得分:0)
同一脚本的另一个版本。它更适合大量配置。它支持祖先配置。您无法单独设置配置 linux 和 master :您必须定义 linux-master 。发生这种情况是因为它禁用或启用每个配置都知道,没有任何配置保持不变。 OTOH,您可以拥有此脚本的两个副本,例如一个用于选择操作系统,另一个用于选择用户访问级别。
setConfig.1.py:
#!/usr/bin/python
# coding: utf-8
# the configuration list format is: one configuration on each line, first configuration name then names of parent configurations, e.g.:
# guest
# basic
# advanced basic
# superuser advanced
# linux
# windows
# customer1 superuser linux
# customer2 basic windows
# customer3 guest windows
CONFIGURATIONS = """
guest
basic
advanced basic
superuser advanced
linux
windows
customer1 superuser linux
customer2 basic windows
customer3 guest windows
"""
import argparse
import os
import re
#set to False for debugging
really = True
verbose = True
configList = filter(None,map(lambda l:l.split(), CONFIGURATIONS.split('\n')))
configParents = { l[0] : l for l in configList }
ancestorSets = {}
def findAncestors(name,visited,tovisit):
visited.add(name)
for a in tovisit:
if not a in visited:
visited.update(findAncestors(a,visited,configParents[a]))
return visited
for c,p in configParents.iteritems():
ancestorSets[c] = findAncestors(c,set(),p)
# In the file 'fname',
# find the text matching "before oldtext after" (all occurrences) and
# replace 'oldtext' with 'newtext' (all occurrences).
# If 'mandatory' is true, raise an exception if no replacements were made.
def fileReplace(fname,before,newtext,after,oldtextpattern=r"[\w.]+",mandatory=True):
with open(fname, 'r+') as f:
read_data = f.read()
pattern = r"("+re.escape(before)+r")"+oldtextpattern+"("+re.escape(after)+r")"
replacement = r"\g<1>"+newtext+r"\g<2>"
new_data,replacements_made = re.subn(pattern,replacement,read_data,flags=re.MULTILINE)
if replacements_made and really:
if new_data!=read_data:
f.seek(0)
f.truncate()
f.write(new_data)
if verbose:
print "patching ",fname," (",replacements_made," occurrence" + ("s" if 1!=replacements_made else ""),")",newtext,("-- no changes" if new_data==read_data else "-- ***CHANGED***")
elif replacements_made:
print "\n\n\n\n"
print fname,":"
print new_data
elif mandatory:
raise Exception("cannot patch the file: "+fname+" with ["+newtext+"] instead of '"+before+"{"+oldtextpattern+"}"+after+"'")
def enableConfigInFile(flavour,fname, enable=True):
if enable:
symbol=r"*"
oldsymbol=re.escape("/")
else:
symbol=r"/"
oldsymbol=re.escape("*")
fileReplace(fname, "/*"+flavour+"{",symbol,"/",oldtextpattern=oldsymbol, mandatory=False)
def enableConfigInXmlFile(flavour,fname, enable=True):
if enable:
startsymbol=r"<!--"
endsymbol=r"-->"
oldstartsymbol=re.escape("X!==")
oldendsymbol=re.escape("==X")
else:
startsymbol=r"X!=="
endsymbol=r"==X"
oldstartsymbol=re.escape("<!--")
oldendsymbol=re.escape("-->")
fileReplace(fname, "<!--"+flavour+"{", endsymbol, "", oldtextpattern=oldendsymbol, mandatory=False)
fileReplace(fname, "", startsymbol, "}"+flavour+"-->", oldtextpattern=oldstartsymbol, mandatory=False)
def enableConfig(flavour, enable=True):
for root, dirs, files in os.walk(".", topdown=True):
# ignore version control directories
# Why .hg- and .git- ? Sometimes we need to hide .hg or .git from programs like `meld`.
# (`meld` ignores .hg if it finds .git)
if '.svn' in dirs:
dirs.remove('.svn')
if '.svn-' in dirs:
dirs.remove('.svn-')
if '.hg' in dirs:
dirs.remove('.hg')
if '.hg-' in dirs:
dirs.remove('.hg-')
if '.git' in dirs:
dirs.remove('.git')
if '.git-' in dirs:
dirs.remove('.git-')
for name in files:
if name.endswith('.java'):
enableConfigInFile(flavour, os.path.join(root, name), enable)
elif name.endswith('.xml'):
enableConfigInXmlFile(flavour, os.path.join(root, name), enable)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
for c in configList:
thisconf = c[0]
parser.add_argument('--'+thisconf, dest='conf', action='store_const', const=thisconf, help='configure as: '+thisconf)
args = parser.parse_args()
if args.conf is None:
parser.print_help()
else:
print "setting configuration: "+args.conf
toenable = ancestorSets[args.conf]
for c in ancestorSets:
print ("enabling " if c in toenable else "disabling ")+c
enableConfig(c, enable=c in toenable)